热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JavaTCP实现高仿版QQ聊天

JavaTCP实现高仿版QQ聊天



Java TCP实现高仿版QQ聊天

前言

? 记录一下这套简陋的系统说明,把所遇到的问题和难点以及操作说明在这篇文档中说明清楚,当个回顾吧。万一以后那一天查看也能及时找到问题。这套系统是在本人大三时期完成的,还存在很多bug。

? 这套聊天程序的完成也从网上借鉴了很多经验。

? 这套系统会发在本人博客园和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。

代码结构


代码结构

Java TCP实现高仿版QQ聊天 - 文章图片

详细说明


com包

Java TCP实现高仿版QQ聊天 - 文章图片

dao包

Java TCP实现高仿版QQ聊天 - 文章图片

view包

Java TCP实现高仿版QQ聊天 - 文章图片

数据库

利用Navicat导入项目中的SQL文件即可。

数据库表


users表

































AccountNickNamePassWordSexBirthSignatureHeadimageLoginIPLocalHostRecentLoginTimeLoginState
账号昵称密码性别生日个性签名头像索引登录IP本地IP最近登录时间登录状态

message表























midmsgsendergettersendtimetype
自增长索引信息正文发送方接收方发送时间消息类型(离线/窗口震动/常规)

friendtable



















IDMySelf_AccountFriends_AccountNote
自增长索引我的账号朋友账号朋友的备注

导入Eclipse

? 项目是一个JavaSE项目,按普通项目导入Eclipse即可。

? 导入后项目若出现红色感叹号,可以右键项目->Build Path->Configure Build Path->移除JRE System Library,然后再通过Add LibraryJRE System Library添加回来即可。

? 若用的数据库版本不一样,可以换成相应版本号的jar包,然后右键jar包->Build Path->Add Build Path->之后若是项目出现红色感叹号,可以在项目的文件夹中找到.classpath文件,然后用记事本打开,删除这一行即可。


? 注意:如果没有更换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,这是客户端唯一的入口。

窗体截图


服务端界面

Java TCP实现高仿版QQ聊天 - 文章图片

登录界面

Java TCP实现高仿版QQ聊天 - 文章图片

注册界面

Java TCP实现高仿版QQ聊天 - 文章图片

?

主界面

Java TCP实现高仿版QQ聊天 - 文章图片

修改个人信息

Java TCP实现高仿版QQ聊天 - 文章图片

添加好友界面

Java TCP实现高仿版QQ聊天 - 文章图片

聊天窗口

Java TCP实现高仿版QQ聊天 - 文章图片

? 左侧是聊天界面,右侧是聊天记录,可以从数据库导入之前的聊天记录,并且聊天时的数据也会进入到聊天记录面板。

查看好友资料/删除好友弹窗

Java TCP实现高仿版QQ聊天 - 文章图片

Java TCP实现高仿版QQ聊天 - 文章图片

离线消息

Java TCP实现高仿版QQ聊天 - 文章图片

? 如果是离线消息或者当对方给我发消息的时候我没有打开和对方的聊天窗口,那么实现弹窗提醒。

有待完善

? 发送文件

? 发送图片

部分代码说明


com.Server.java

@Override
public 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 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();
}

@Override
public 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,备注CSDN博客。



推荐阅读
  • Amoeba 通过优化 MySQL 的读写分离功能显著提升了数据库性能。作为一款基于 MySQL 协议的代理工具,Amoeba 能够高效地处理应用程序的请求,并根据预设的规则将 SQL 请求智能地分配到不同的数据库实例,从而实现负载均衡和高可用性。该方案不仅提高了系统的并发处理能力,还有效减少了主数据库的负担,确保了数据的一致性和可靠性。 ... [详细]
  • 服务器部署中的安全策略实践与优化
    服务器部署中的安全策略实践与优化 ... [详细]
  • 深入解析HTML5字符集属性:charset与defaultCharset
    本文将详细介绍HTML5中新增的字符集属性charset和defaultCharset,帮助开发者更好地理解和应用这些属性,以确保网页在不同环境下的正确显示。 ... [详细]
  • 本文详细介绍了在 Ubuntu 系统上搭建 Hadoop 集群时遇到的 SSH 密钥认证问题及其解决方案。通过本文,读者可以了解如何在多台虚拟机之间实现无密码 SSH 登录,从而顺利启动 Hadoop 集群。 ... [详细]
  • Linux CentOS 7 安装PostgreSQL 9.5.17 (源码编译)
    近日需要将PostgreSQL数据库从Windows中迁移到Linux中,LinuxCentOS7安装PostgreSQL9.5.17安装过程特此记录。安装环境&#x ... [详细]
  • 本文介绍了在 Spring Boot 中使用 JPA 进行数据删除操作时遇到的 SQL 错误及其解决方法。错误表现为:删除操作失败,原因是无法打开 JPA EntityManager 以进行事务处理。 ... [详细]
  • 解决Bootstrap DataTable Ajax请求重复问题
    在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
  • 如何在Java中使用DButils类
    这期内容当中小编将会给大家带来有关如何在Java中使用DButils类,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。D ... [详细]
  • 思科IOS XE与ISE集成实现TACACS认证配置
    本文详细介绍了如何在思科IOS XE设备上配置TACACS认证,并通过ISE(Identity Services Engine)进行用户管理和授权。配置包括网络拓扑、设备设置和ISE端的具体步骤。 ... [详细]
  • 本文对SQL Server系统进行了基本概述,并深入解析了其核心功能。SQL Server不仅提供了强大的数据存储和管理能力,还支持复杂的查询操作和事务处理。通过MyEclipse、SQL Server和Tomcat的集成开发环境,可以高效地构建银行转账系统。在实现过程中,需要确保表单参数与后台代码中的属性值一致,同时在Servlet中处理用户登录验证,以确保系统的安全性和可靠性。 ... [详细]
  • 在使用 Cacti 进行监控时,发现已运行的转码机未产生流量,导致 Cacti 监控界面显示该转码机处于宕机状态。进一步检查 Cacti 日志,发现数据库中存在 SQL 查询失败的问题,错误代码为 145。此问题可能是由于数据库表损坏或索引失效所致,建议对相关表进行修复操作以恢复监控功能。 ... [详细]
  • Java Socket 关键参数详解与优化建议
    Java Socket 的 API 虽然被广泛使用,但其关键参数的用途却鲜为人知。本文详细解析了 Java Socket 中的重要参数,如 backlog 参数,它用于控制服务器等待连接请求的队列长度。此外,还探讨了其他参数如 SO_TIMEOUT、SO_REUSEADDR 等的配置方法及其对性能的影响,并提供了优化建议,帮助开发者提升网络通信的稳定性和效率。 ... [详细]
  • 本文介绍了如何利用Struts1框架构建一个简易的四则运算计算器。通过采用DispatchAction来处理不同类型的计算请求,并使用动态Form来优化开发流程,确保代码的简洁性和可维护性。同时,系统提供了用户友好的错误提示,以增强用户体验。 ... [详细]
  • 在 Axublog 1.1.0 版本的 `c_login.php` 文件中发现了一个严重的 SQL 注入漏洞。该漏洞允许攻击者通过操纵登录请求中的参数,注入恶意 SQL 代码,从而可能获取敏感信息或对数据库进行未授权操作。建议用户尽快更新到最新版本并采取相应的安全措施以防止潜在的风险。 ... [详细]
  • 在Linux系统中,网络配置是至关重要的任务之一。本文详细解析了Firewalld和Netfilter机制,并探讨了iptables的应用。通过使用`ip addr show`命令来查看网卡IP地址(需要安装`iproute`包),当网卡未分配IP地址或处于关闭状态时,可以通过`ip link set`命令进行配置和激活。此外,文章还介绍了如何利用Firewalld和iptables实现网络流量控制和安全策略管理,为系统管理员提供了实用的操作指南。 ... [详细]
author-avatar
Cindy_J_Lee
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有