Java TCP实现高仿版QQ聊天(一)
前言
记录一下这套简陋的系统说明,把所遇到的问题和难点以及操作说明在这篇文档中说明清楚,当个回顾吧,万一以后那一天查看也能及时找到问题。这套系统是在本人大三时期完成的,从GitHub上借鉴了很多经验,也借用了一些界面,很感谢。但目前这套系统还存在很多bug,之后有时间会慢慢进行更新。
我把这套QQ程序分为了两部分,第一部分也就是本文,主要讲述源码和原理以及各种工具的使用,最重要的聊天部分是只实现在本机进行通讯,也就是说两台机器就无法进行通讯。第二部分我将会说明如何实现不同机器之间的通讯,也就真正意义上的QQ聊天,觉得小弟写的还行的可以关注一下,第二部分参考《Java TCP实现高仿版QQ聊天(二)》
另外,最近做了使用JSP做了一套资料共享平台和智能OA办公系统,有需要的伙伴可以点击链接直达,都是可以提供源码的哦!
这套系统会发在本人博客园和CSDN博客做个记录。
环境配置说明
1、JDK用的是1.8版本
2、开发工具使用的是eclipse Version: 2019-12。
3、数据库用的是MySQL 8.0.17 Community Server 。
4、MySQL和Java连接用的是mysql-connector-java-8.0.16.jar。
5、代码的编码格式为GBK。
6、此外还用到了Photoshop、Navicat等工具。
因为之前没有了解到eclipse的swing插件画图,swing界面都是一个一个敲出来了,当然了,有一部分也是从网友那边借鉴过来的。想说的是用swing的时候用插件画图可以省去很多时间以及可能,推荐大家使用。
一般聊天的时候使用的是UDP协议,但在本系统中全部使用TCP协议,没有使用UDP。
代码结构
代码结构
详细说明
com包
dao包
view包
数据库
利用Navicat导入项目中的SQL文件即可。
数据库表
users表
Account | NickName | PassWord | Sex | Birth | Signature | Headimage | LoginIP | LocalHost | RecentLoginTime | LoginState |
---|---|---|---|---|---|---|---|---|---|---|
账号 | 昵称 | 密码 | 性别 | 生日 | 个性签名 | 头像索引 | 登录IP | 本地IP | 最近登录时间 | 登录状态 |
message表
mid | msg | sender | getter | sendtime | type |
---|---|---|---|---|---|
自增长索引 | 信息正文 | 发送方 | 接收方 | 发送时间 | 消息类型(离线/窗口震动/常规) |
friendtable
ID | MySelf_Account | Friends_Account | Note |
---|---|---|---|
自增长索引 | 我的账号 | 朋友账号 | 朋友的备注 |
导入Eclipse
项目是一个JavaSE项目,按普通项目导入Eclipse即可。
导入后项目若出现红色感叹号,可以右键项目->Build Path->Configure Build Path->移除JRE System Library
,然后再通过Add Library
把JRE System Library
添加回来即可。
若用的数据库版本不一样,可以换成相应版本号的jar包,然后右键jar包->Build Path->Add Build Path->之后若是项目出现红色感叹号,可以在项目的文件夹中找到.classpath文件,然后用记事本打开,删除这一行即可。
<classpathentry kind="lib" path="lib/mysql-connector-java-8.0.16.jar"/>
注意:如果没有更换jar包或者没有更换jar包后没有出现感叹号,不需要上一步操作。
修改配置
打开utils.MyTools.java文件设置QQServerPort、QQServerIP,也就是运行时的端口号和IP地址,一般端口号不需要进行更改,修改QQServerIP即可(本系统测试时就是127.0.0.1,也就是本地IP地址,一般都是这个,如果可以运行就不需要改)。
数据库配置文件放在项目根目录下面的db.properties文件中,这个是肯定需要改的,这个怎么操作自行百度。这些信息是通过utils.PropertiesUtils.java进行IO读取然后读取到 JDBCUtils中。
启动
第一步:启动服务端,view.ServerFrame.java,点击按钮“启动服务器”按钮。
第二步:启动客服端,view.LoginFrame.java,这是客户端唯一的入口。
窗体截图
服务端界面
登录界面
注册界面
主界面
修改个人信息
添加好友界面
聊天窗口
左侧是聊天界面,右侧是聊天记录,可以从数据库导入之前的聊天记录,并且聊天时的数据也会进入到聊天记录面板。
查看好友资料/删除好友弹窗
离线消息
如果是离线消息或者当对方给我发消息的时候我没有打开和对方的聊天窗口,那么实现弹窗提醒。
有待完善
发送文件
发送图片
部分代码说明
com.Server.java
@Overridepublic void run() {try {//1.设置服务器套接字 ServerSocket(int port)创建绑定到指定端口的服务器套接字server = new ServerSocket(MyTools.QQServerPort);while(isRunning) {//2.阻塞式等待客户端连接 (返回值)Socket accept()侦听要连接到此套接字的客户端并接受它。client = server.accept();System.out.println("一个客户端已连接....");input = new ObjectInputStream(client.getInputStream());output = new ObjectOutputStream(client.getOutputStream());String text = (String)input.readObject();//将客服端发来的信息转换为StringString[] temp = text.split(MyTools.FLAGEND);//对客户端发来的信息进行分割Flag flag = MyTools.stringToFlagEnum(temp[0]);//获得标志String congtent = temp[1];//客服端发送过来的正文dealWithMessage(flag,congtent);}} catch (IOException e) {close(output,input,client,server);//释放资源} catch(ClassNotFoundException e){e.printStackTrace();}}/** * 按照flag,进行对应的函数操作* @param flag 标志* @param message 文本正文*/public void dealWithMessage(Flag flag,String message) {switch (flag) {case REGISTER:doUserRegister(message);break;//处理注册请求case LOGIN:doUserLogin(message);break;//处理登录请求case ADD_Query:doAddUsersQuery(message);break;//处理添加好友查询请求case ADD:doAddUsersAdd(message);break;//处理添加好友请求case DeleteUsers:doDeleteUsers(message);break;//处理删除好友请求case GET_FRIEND_INFO:doGetFriendInfo(message);break;//处理查看好友资料
// case SettingFriendNote:doSettingFriendNote(message);break;//设置备注case GET_HeadImage:dogetUsersInfo(message);break;//处理获取头像请求case SHOWHISTORY:doShowHistory(message);break;//将数据库中的聊天记录设置在聊天记录面板case UpdateUsersInfo:doUpdateUsers(message);break;//修改用户资料case UpdateUsersPass:doUpdateUsersPass(message);break;//修改用户密码case GetNotOnlineMsg:dogetUnReadMsg(message);break;//获取离线消息default:break;}}
这部分代码是开启一个线程从com.LoginUser.java读取IO流发送过来的数据,因为com.LoginUser.java发送来的数据进行了字符串拼接,也就是每个方法对应的处理有一个枚举,然后在本方法中进行字符串分割,传入dealWithMessage(Flag flag,String message)方法中,然后进行switch判断,调用相应的方法。
utils.MyTools.java
public enum Flag{REGISTER,//注册REGISTER_SUCCEED,//注册成功REGISTER_FAILED, //注册失败ALREADY_REGISTER,//已经注册LOGIN,//登录LOGIN_SUCCEED,//登录成功LOGIN_FAILED,//登录失败ALREADY_LOGIN,//已登录UNLOAD_LOGIN,//退出登录............};
这是定义枚举,也就是相应的操作数据定义一个枚举,方便判断IO流中的数据是需要进行什么操作。相当于一个标志了,在发送IO流数据的前面加上这些字段,可以让IO流接收方判断字段信息是准备进行什么操作。
utils.PropertiesUtils.java
public class PropertiesUtils {private static Properties pro;static{pro = new Properties();try {//把配置文件的信息加载到 pro对象中pro.load(new FileInputStream("db.properties"));} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}//根据属性名来获取属性值 public static String getPropertiesValue(String key){return pro.getProperty(key);}
}
读取db.properties文件中数据库的配置文件,把数据库信息放置在文件中的好处是当改数据库信息的时候不需要对代码进行改动。
view.ChatFrame.java
public void showMessage(JTextPane jtp, Message msg, boolean fromSelf) {//设置显示格式SimpleAttributeSet attrset = new SimpleAttributeSet();StyleConstants.setFontFamily(attrset, "仿宋");StyleConstants.setFontSize(attrset,14);Document docs = jtp.getDocument();String info = null;try {if(fromSelf){//发出去的消息内容,发送的内容从本类中获取info = msg.getSendTime()+" ";//发送时间:绿色StyleConstants.setForeground(attrset, Color.blue);StyleConstants.setFontSize(attrset,16);StyleConstants.setBold(attrset, false);StyleConstants.setItalic(attrset, false);docs.insertString(docs.getLength(), info, attrset);info = "我:\n";//自己账号:紫色StyleConstants.setForeground(attrset, Color.blue);StyleConstants.setFontSize(attrset,16);StyleConstants.setBold(attrset, false);StyleConstants.setItalic(attrset, false);docs.insertString(docs.getLength(), info, attrset); info = " "+msg.getContent()+"\n";//发送内容:黑色StyleConstants.setFontSize(attrset,20);StyleConstants.setForeground(attrset, Color.black);StyleConstants.setBold(attrset, true);StyleConstants.setItalic(attrset, true);docs.insertString(docs.getLength(), info, attrset);}else{//接收到的消息内容,接收的内容是ClientToServerThread类调用该方法info = msg.getSendTime()+" ";//发送时间:绿色StyleConstants.setForeground(attrset, Color.red);StyleConstants.setFontSize(attrset,16);StyleConstants.setBold(attrset, false);StyleConstants.setItalic(attrset, false);docs.insertString(docs.getLength(), info, attrset);info = msg.getSenderName()+"("+msg.getSenderId()+"):\n";//对方账号:红色StyleConstants.setForeground(attrset, Color.red);StyleConstants.setFontSize(attrset,16);StyleConstants.setBold(attrset, false);StyleConstants.setItalic(attrset, false);docs.insertString(docs.getLength(), info, attrset); info = " "+msg.getContent()+"\n";//发送内容:蓝色StyleConstants.setFontSize(attrset,20);StyleConstants.setForeground(attrset, Color.black);StyleConstants.setBold(attrset, true);StyleConstants.setItalic(attrset, true);docs.insertString(docs.getLength(), info, attrset);}} catch (BadLocationException e) {e.printStackTrace();}}
传入的参数为(JTextPane jtp,Message msg,boolean fromSelf),jtp表示的是在哪个JTextPane中展示,msg表示聊天内容,fromSelf(true表示发送方,false表示接收方),然后下面的代码是对接收方和发送方的格式进行设置,代码中表示发送方为蓝色,接收发为红色。便于判断信息是发送还是接收。
com.ManageFriendListFrame.java
public class ManageFriendListFrame {private static Hashtable<String, MainFrame> friendListFrames = new Hashtable<>();public static void addFriendListFrame(String frameName,MainFrame fl){friendListFrames.put(frameName,fl);}public static MainFrame getFriendListFrame(String frameName){return friendListFrames.get(frameName);}public static MainFrame removeFriendListFrame(String frameName){return friendListFrames.remove(frameName);}
}
这个类是管理用户的聊天界面的,也就是说一个用户只能打开一个主界面。主要是用Hashtable来管理,后来网上查了一下,Hashtable的父类已经过时,现在使用HashMap比较好。
Hashtable和HashMap的区别主要在于:
1、继承的父类不同
HashMap继承自AbstractMap类。但二者都实现了Map接口。
Hashtable继承自Dictionary类,Dictionary类是一个已经被废弃的类(见其源码中的注释)。父类都被废弃,自然而然也没人用它的子类Hashtable了。
2、HashMap线程不安全,HashTable线程安全
…
TCP协议
com.LoginUser.java
客户端调用本类实现TCP发送,由于篇幅有限,就只截取部分代码示例。构造方法初始化了Socket,ObjectOutputStream和ObjectInputStream,然后在sendLoginInfoToServer方法中把需要发送的方法组成字符串,在通过对象输出流output.writeObject(text)输出给Server.java;
再利用对象输入流接收结果并转换为实体类对象,Message msg = (Message) input.readObject(),就可以直接使用get方法获取到服务端发送过来的值。
public class LoginUser {private Socket client;private ObjectOutputStream output;private ObjectInputStream input;public LoginUser(){try {client = new Socket(MyTools.QQServerIP, MyTools.QQServerPort);output = new ObjectOutputStream(client.getOutputStream());input = new ObjectInputStream(client.getInputStream());} catch (IOException e) {System.out.println("连接服务器失败!");e.printStackTrace();}}/*** 将通过校验的登录信息发送到服务器* 并将得到的消息包返回(包含当前用户的所有好友)*/public Message sendLoginInfoToServer(JFrame f,Users users){//把要发送的信息组成字符串String text = Flag.LOGIN+MyTools.FLAGEND+users.getAccount()+MyTools.SPLIT1+users.getPassword()+MyTools.SPLIT1+users.getLoginState()+MyTools.SPLIT1+users.getRecentLoginTime()+MyTools.SPLIT1+users.getLoginIP()+MyTools.SPLIT1+users.getLocalHost();//检测用户输入信息是否合理Users u = checkLoginInfo(f,users.getAccount(),users.getPassword());if(u != null){try {output.writeObject(text);//发送到服务器Message msg = (Message) input.readObject();//接收返回结果if(msg.getType() == Flag.LOGIN_SUCCEED){//登录成功ClientToServerThread th = new ClientToServerThread(client);th.start();//创建与服务器通信线程ManageThread.addThread(users.getAccount(),th);//为登录者开启一个线程return msg;} else if(msg.getType() == Flag.LOGIN_FAILED){JOptionPane.showMessageDialog(f, "账号或密码输入错误,请重新输入!");} else if(msg.getType() == Flag.ALREADY_LOGIN){JOptionPane.showMessageDialog(f, "该用户已登录,请勿重复操作!");}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}return null;}
com.Server.java
实现Runnable接口,定义ServerSocket,ObjectInputStream,ObjectOutputStream。开启一个线程接收LoginUser.java发送过来的数据,派出客服代表与之联系client = server.accept();然后把输入流转换为字符串,再把字符串分割为字符串数组,看服务端发送的是什么请求,之后调用dealWithMessage()方法进行处理。
在相对应的方法中把数据库操作返回的结果通过输出流发送给LoginUser.java。
这边完成了TCP之间的沟通和交流。
public class Server implements Runnable{private ServerSocket server;private Socket client;private ObjectInputStream input;private ObjectOutputStream output;private volatile boolean isRunning;Message msg = new Message();UserDao ud = new UserDao();MsgDao md = new MsgDao();public Server(){isRunning = true;new Thread(this).start();}@Overridepublic void run() {try {//1.设置服务器套接字 ServerSocket(int port)创建绑定到指定端口的服务器套接字server = new ServerSocket(MyTools.QQServerPort);while(isRunning) {//2.阻塞式等待客户端连接 (返回值)Socket accept()侦听要连接到此套接字的客户端并接受它。client = server.accept();input = new ObjectInputStream(client.getInputStream());output = new ObjectOutputStream(client.getOutputStream());String text = (String)input.readObject();//将客服端发来的信息转换为StringString[] temp = text.split(MyTools.FLAGEND);//对客户端发来的信息进行分割Flag flag = MyTools.stringToFlagEnum(temp[0]);//获得标志String congtent = temp[1];//客服端发送过来的正文dealWithMessage(flag,congtent);}} catch (IOException e) {close(output,input,client,server);//释放资源} catch(ClassNotFoundException e){e.printStackTrace();}}public void dealWithMessage(Flag flag,String message) {switch (flag) {case REGISTER:doUserRegister(message);break;//处理注册请求case LOGIN:doUserLogin(message);break;//处理登录请求case ADD_Query:doAddUsersQuery(message);break;//处理添加好友查询请求case ADD:doAddUsersAdd(message);break;//处理添加好友请求case DeleteUsers:doDeleteUsers(message);break;//处理删除好友请求case GET_FRIEND_INFO:doGetFriendInfo(message);break;//处理查看好友资料case GET_HeadImage:dogetUsersInfo(message);break;//处理获取头像请求case SHOWHISTORY:doShowHistory(message);break;//将数据库的聊天记录放在聊天记录面板case UpdateUsersInfo:doUpdateUsers(message);break;//修改用户资料case UpdateUsersPass:doUpdateUsersPass(message);break;//修改用户密码case GetNotOnlineMsg:dogetUnReadMsg(message);break;//获取离线消息default:break;}
代码整理不易,需要源码的可以联系企鹅号863772270,请作者喝瓶水即可
Java TCP实现高仿版QQ聊天(一)相关推荐
- Java TCP实现高仿版QQ聊天(二)
前言 这是在上一篇博客基础上开展的,第一部分我们只实现了本机的聊天,无法将程序放置另外机器上和本机进行聊天.这篇博客我将介绍如何实现不同机器之间实现聊天,达到真正意义上的聊天.不过这篇博客在其他机 ...
- java ing印版_Java TCP实现高仿版QQ聊天(二)
前言 这是在上一篇博客基础上开展的,第一部分我们只实现了本机的聊天,无法将程序放置另外机器上和本机进行聊天.这篇博客我将介绍如何实现不同机器之间实现聊天,达到真正意义上的聊天.不过这篇博客在其他机 ...
- java毕业设计——基于java+TCP+UDP的局域网聊天室系统设计与实现(毕业论文+程序源码)——局域网聊天室系统
基于java+TCP+UDP的局域网聊天室系统设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于java+TCP+UDP的局域网聊天室系统设计与实现,文章末尾附有本毕业设计的论文和源码下载地 ...
- java课程设计qq,模块java课程设计报告qq聊天
河南工业大学 课程设 计 课程设计名称: ja a qq聊天系统 学生姓名 : x aoy 指导教 师: 王高平 课程设计时间: 2016.7.7 计科 专业课程设计任务书 说明: ...
- Java+Swing+mysql仿QQ聊天工具
Java+Swing+mysql仿QQ聊天工具 一.系统介绍 二.功能展示 1.用户登陆 2.好友列表 3.好友聊天 4.服务器日志 三.系统实现 四.其它 1.其他系统实现 2.获取源码 一.系统介 ...
- Java TCP简易多人聊天室
一.说明: 本例是一个简易的Java TCP多人聊天室,先启动服务器端,再启动客户端,客户端敲入用户名,然后可以开始聊天,敲入信息,每一个在线的用户都会收到相应信息. 演示如下图: 二.服务器端代 ...
- 【高级Java开发】类QQ聊天工具的设计与实现(代码已上传)
1.要求 类QQ聊天工具的设计与实现,要求如下: 用户可注册并通过密码录登. 用户可通过查找添加自己的好友. 用户可通过好友列表选择好友并与之对话. 需实现聊天记录查看功能. 界面美观大方,交互设计尽 ...
- 基于java实现简单的QQ聊天通信功能
数据存储结构 1.数据库设计 数据库是该即时通讯系统的中枢结构,进行信息处理的各个部分都是在数据库的基础上的,从而能够达成对于各类数据安全完整的存储,并进行合理的运用.有必要时可以设置数据字典,数据字 ...
- 韩顺平Java自学笔记 项目 QQ聊天室
目录 一.项目前的准备 1.为什么选择这个项目 2.项目开发的流程 3.项目的需求 二.开发阶段 1.登录功能实现 2.拉取在线用户实现 3.无异常退出的实现 4.私聊功能的实现 5.群发的实现 6. ...
最新文章
- hdfs namenode -format 初始化创建不了目录的问题
- VB.NET实现DirectDraw9 (1) 托管的DDraw
- shell grep sed awk 练习
- 如何将Groovy中的文件读入字符串?
- Swift网络开发之NSURLSession学习笔记
- React.createClass和extends Component的区别
- vue的一些坑(第二天)
- 8. Sphinx 站内搜索
- linux磁盘满了怎么转移,linux 磁盘转移空间的方法
- Mysql 启动命令详解
- js 百度、高德、谷歌、火星、wgs84(2000)地图坐标相互转换的JS实现
- pig java api_Pig安装及入门案例
- win7系统提示“此windows副本不是正版” 解决方案
- Linux -- snmp v3开通、认证、加密及其他
- Redis常用数据结构及其场景归纳
- ESP8266 WIFI模块调试及在QT Windows下的通讯
- 上海工程技术大学c语言试卷,上海工程技术大学2009_2010C语言试卷A.doc
- html查看器更改默认打开方式,初学者如何设置默认打开方式
- 注意力机制(Attention)
- ajax返回报错html,Jquery AJAX POST调用返回200状态确定,但错误
热门文章
- 矩阵求和c语言通俗易懂
- 使用vue echarts 制作地图map
- 华为Android10版怎么截屏,华为畅享10e怎么截屏?畅享10e多种屏幕截图方法
- win7笔记本无线网服务器,win7笔记本连接不上无线网络
- 从 几 个应用入手 了解为什么灵魂绑定代币将为 DeFi 带来大规模采用
- S3C6410(OK6410开发板介绍)
- 智能音箱的差评|为什么我要买一堆垃圾回来吃灰!
- Ambiguous method overloading for method ****** 异常的解决办法
- 使用JavaBean封装对数据库的访问、连接池的配置和使用
- 从互动直播到在线抓娃娃,实时视频超低延迟架构的思考与实践