目录
PS:这次项目主要是补齐上一个项目:【 Java实现--基于服务器的多用户聊天室】遗留下的功能(文件传输,保存聊天记录,一对一聊天,服务器端关闭服务器并通知客户端退出,采用数据库检验用户账号和密码的正确性。)
一. 项目简介:
QQ通信程序,主要添加登录部分与文件传输部分。
其中,登录部分实现安全登录和安全注册,安全登录部分,用户密码从客户机到服务器以加密形式传输;安全注册部分,用户注册时,用户密码在数据库中以加密形式保存。文件传输部分实现基于SSL的安全传输,文件传输的同时可传送加密的数字签名。
在本项目中,信息传递、文件发送过程采用阻塞或非阻塞方式。
二. 开发环境:
NetBeans IDE 8.2、JDK 1.8、MySQL 8.0.19
三. 项目具体功能:
服务器端:
A. 启动服务器按钮:启动服务器功能;
B. 聊天室大厅消息显示文本域:聊天室消息显示功能;
C. 聊天室在线用户显示文本域:在线用户显示功能。
客户端:
1. 登录功能
A. 进入聊天室按钮:登录进入聊天室功能;
B. 退出聊天室按钮:退出聊天室功能;
2. 注册功能
A. 注册按钮:注册QQ聊天室账号功能;
B. 取消按钮:取消注册账号功能;
3. 聊天室功能
3.1 QQ聊天室主界面
A. 发送文本按钮:文本信息发送功能;
B. 文件传输工具栏:上传文件到服务器功能;
C. 私聊选择框:单人聊天模式转换功能;
D. 清除聊天记录按钮:清除用户聊天记录功能;
E. 聊天室在线用户显示文本域:在线用户显示功能;
F. 退出聊天室:退出聊天室功能;
3.2 私聊界面
A. 聊天信息文本域:显示聊天信息以及对方用户名;
B. 发送按钮:文本信息发送功能。
四. 运行结果:
1. 服务器端界面:
图表 1 QQ聊天室服务器端主界面
图表 2 文件传输成功后服务器端界面
图表 3 upload文件夹新增服务器接收的文件
2. 客户端界面:
图表 4 用户“111”登录界面 图表 5 登录成功聊天室主界面
图表 6 客户端选择上传的文件 图表 7 用户正在上传文件
图表 9 注册用户“222”界面
图表 10 注册后进行登录页面
---------------------------------------------------- 分割线 ------------------------------------------------------------------
【以下部分为具体的包结构及其作用、代码讲解~】
五. QQClient包结构:
图表 11 QQClient包结构
1. com.example包
A. ClientUI.java: 客户机会话界面
图表 12 客户机会话界面
ClientUI中主要的成员方法如下:
private void btnSendActionPerformed(java.awt.event.ActionEvent evt):
“发送”按钮监听器,实现文本发送功能;
private void txtInputActionPerformed(java.awt.event.ActionEvent evt):
键盘监听器,实现按下回车键,也能发送消息;
private void formWindowClosing(java.awt.event.WindowEvent evt):
窗体事件,单击窗体“关闭”按钮,关闭窗体之前发送M_QUIT下线消息;
private void userListMouseClicked(java.awt.event.MouseEvent evt):
列表监听器,实现JList用户列表“双击”进入私聊界面事件,创建客户机界面,显示会话窗体功能;
private void btnClearActionPerformed(java.awt.event.ActionEvent evt):
“清除聊天记录”按钮监听器,实现清空聊天消息;
private void btnFileMousePressed(java.awt.event.MouseEvent evt):
工具栏“文件传输”按钮监听器,点击实现弹出菜单;
private void uploadFileActionPerformed(java.awt.event.ActionEvent evt):
“选择上传文件”的事件过程。
B. FileSender.java:发送文件的后台线程类
在客户机发送文件线程的基础上,增加了数字签名机制和密钥机制。其主要逻辑如下:
- 发送文件名称、文件长度;
- 传送文件内容;
- 发送加密后的数字签名;
- 发送加密密钥;
- 接收服务器反馈信息。
主要逻辑对比如下图所示:
图表 13 客户机发送文件逻辑 图表 14 带数字签名的文件发送逻辑
C. LoginUI.java: 客户机登录界面
图表 15 客户机登录界面
LoginUI中主要的成员方法如下:
private void btnLoginActionPerformed(java.awt.event.ActionEvent evt):
“进入聊天室”按钮监听器,实现用户登录验证、创建客户机界面、设置会话标题、显示会话窗体等功能;
private void btnQuitActionPerformed(java.awt.event.ActionEvent evt):
“退出聊天室”按钮监听器,实现退出功能;
private void btnRegisterActionPerformed(java.awt.event.ActionEvent evt):
“注册账号”按钮监听器,实现用户注册功能,跳转至注册页面。
D. PrivateChatUI.java: 客户机私聊界面
图表 16 用户私聊界面
PrivateChatUI主要的成员方法如下:
private void btnSendActionPerformed(java.awt.event.ActionEvent evt):
“发送”按钮监听器,实现文本发送功能;
private void txtInputActionPerformed(java.awt.event.ActionEvent evt):
键盘监听器,实现按下回车键,也能发送消息;
E. ReceiveMessage.java: 客户机接收消息的线程类
图表 17 QQ消息运行的时序逻辑
客户机消息接收线程的逻辑代码如下:
package com.example;
/**
* 客户机接收消息线程
* @author Jacky
*/
public class ReceiveMessage extends Thread {
private DatagramSocket clientSocket; //会话套接字
private ClientUI parentUI; //父类
private byte[] data = new byte[8096];//8KB数组
private DefaultListModel listModel = new DefaultListModel(); //用户列表Model
private PrivateChatUI priChatUI; //私聊父类
//构造函数
public ReceiveMessage(DatagramSocket socket, ClientUI parentUI) {
clientSocket = socket; //会话套接字
this.parentUI = parentUI; //父类
}
public ReceiveMessage(DatagramSocket socket, PrivateChatUI priChatUI) {
clientSocket = socket; //会话套接字
this.priChatUI = priChatUI; //父类
}
@Override
public void run(){
while(true){ //无限循环,处理收到的各类消息
try{
DatagramPacket packet = new DatagramPacket(data, data.length);
//构建接收报文
clientSocket.receive(packet); //接收
Message msg = (Message)Translate.ByteToObject(data);
//还原消息对象
String userId = msg.getUserId(); //当前用户id
//根据消息类型分类处理
if(msg.getType().equalsIgnoreCase("M_LOGIN")){
//其他用户的登录消息
playSound("/com/example/sound/fadeIn.wav");
//上线提示音
//更新消息窗口
parentUI.txtArea.append(userId + " 进入聊天室...\n");
//新上线用户加入列表
listModel.add(listModel.getSize(), userId);
parentUI.userList.setModel(listModel);
}else if(msg.getType().equalsIgnoreCase("M_ACK")){
//服务器确认消息
//登录成功,将自己加入用户列表
listModel.add(listModel.getSize(), userId);
parentUI.userList.setModel(listModel);
}else if(msg.getType().equalsIgnoreCase("M_MSG")){
//普通会话消息
playSound("/com/example/sound/msg.wav"); //消息提示音
//更新消息窗口
parentUI.txtArea.append(userId + ":" + msg.getText() + "\n");
}else if(msg.getType().equalsIgnoreCase("M_QUIT")){
//其他用户下线消息
playSound("/com/example/sound/leave.wav"); //消息提示音
//更新消息窗口
parentUI.txtArea.append(userId + " 退出聊天室...\n");
//下线用户从列表删除
listModel.remove(listModel.indexOf(userId));
parentUI.userList.setModel(listModel);
}else if(msg.getType().equalsIgnoreCase("M_PRIVATE")){ //私聊会话消息
playSound("/com/example/sound/msg.wav"); //消息提示音
//更新消息窗口
parentUI.txtArea.append(userId + " 对 " + msg.getTargetId() + "说:" + msg.getText() + "\n");
}//end if
}catch(IOException ex){
JOptionPane.showMessageDialog(null, ex.getMessage(),
"错误提示",JOptionPane.ERROR_MESSAGE);
}//end try
}//end while
}//end run
//播放声音文件,@param filename 声音文件路径和名称
private void playSound(String filename){
URL url = AudioClip.class.getResource(filename);
AudioClip sound;
sound = Applet.newAudioClip(url);
sound.play();
}
}//end class
F. RegistUI.java:客户机用户注册界面
图表 18 用户注册界面
RegistUI中主要的成员方法如下:
private void btnRegisterActionPerformed(java.awt.event.ActionEvent evt):
“注册”按钮监听器,实现用户注册验证、创建客户机登录界面、设置会话标题、显示会话窗体等功能。
2. com.example.db.beans包
Member.java:与QQDB数据库的Member数据表对应的Java实体类。
用数据表的名称命名实体类,在该类中,各属性的定义与数据表Member中的字段一一对应,保持属性名与字段名相同,数据类型一致。
Member类定义如下:
public class Member { //定义实体类,属性名与字段名相同,类型也相同
private int id; //对应数据表中的id
private String name; //对应数据表中的name
private String password; //对应数据表中的password
private String email; //对应数据表中的email
private Timestamp time; //对应数据表中的time
private String headImage;//对应数据表中的headImage
}
3. com.example.keystore包
A. client.keystore:该库文件包含客户机与服务器的私钥与公钥。
B. tclient.keystore:该库文件包含服务器的公钥。
4. com.example.resource资源包
存放QQClient项目中各个页面显示的图片,如login.png、logo.png、download.png、file.png、remote.png等。
5. com.example.security包
Cryptography.java:安全类,定义了项目中用到的加密解密和生成密钥的方法。
主要代码如下:
package com.example.security;
/**
* Cryptography类,用于实现一些通用的加密解密算法。包括如下功能函数
* getHash,对字符串明文计算摘要,主要用于密码加密传输和将密码以摘要形式保存于数据库
* generateNewKey,生成AES对称密钥
* @author Jacky
*/
public class Cryptography {
private static final int BUFSIZE = 8192; //缓冲区大小
/**
* getHash,消息摘要算法,实现明文加密功能
* @param plainText :待加密的明文
* @param hashType :算法类型:"MD5"、"SHA-1"、"SHA-256"、"SHA-384"、"SHA-512"
* @return 密文的16进制字符串
*/
public static String getHash(String plainText,String hashType) {
try {
MessageDigest md = MessageDigest.getInstance(hashType); //算法
byte[] encryptStr = md.digest(plainText.getBytes("UTF-8")); //摘要
return DatatypeConverter.printHexBinary(encryptStr); //16进制字符串
} catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
return null;
}
}
/**
* 用AES对称加密算法,生成一个新的密钥
* @return 生成的密钥
*/
public static SecretKey generateNewKey() {
try {
//密钥生成器
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128); //128,192,256
SecretKey secretKey = keyGenerator.generateKey(); //新密钥
return secretKey;
} catch (NoSuchAlgorithmException ex) {
return null;
}
}
}//end class
6. com.example.sound包
存放QQClient项目中的声音素材,如用户的上线提示音(fadeIn.wav)、会话消息提示音(msg.wav)、下线提示音(leave.wav)等。
7. com.example.util包
A. Message.java: 消息类
在本项目中,主要围绕以下5方面定义Message消息类。
- 消息类型:例如登录消息、普通的文本消息、用户下线消息等。
- 消息内容:具体的消息字符串,对于命令类消息,可以为空。
- 消息的目标主机:消息要到达的目标主机地址与端口。
- 消息来源:发送消息的用户id。
- 消息的目标用户id:消息要到达的目标用户id,实现用户一对一的私聊功能。
分别如下:
private String userId = null; //用户id
private String password = null;//密码
//消息类型:M_LOGIN:用户登录消息;M_SUCCESS:登录成功;M_FAULER:登录失败
//M_ACK:服务器对登录用户的回应消息;M_MSG:会话消息;M_QUIT:用户退出消息
//M_REGIST:用户注册消息;M_RESUCCESS:注册成功
//M_PRIVATE:用户私聊消息
private String type = null;
private String text = null; //消息体
private InetAddress toAddr = null; //目标用户地址
private int toPort; //目标用户端口
private String targetId = null; //目标用户id
B. Translate.java: 转换类
将Message类中的消息序列化,以流的方式传输。发送消息前也需要将对象序列化为字节流,接收消息时又需要将字节流还原为对象,在Translate类中单独定义。
Translate.java代码如下:
package com.example.util;
/**
*
* @author Jacky
*/
public class Translate {
/**
* 对象转化为字节数组形式,实现对象序列化
* @param obj 待转化的对象
* @return 字节数组
*/
public static byte[] ObjectToByte(Object obj){
byte[] buffer = null;
try{
ByteArrayOutputStream bo = new ByteArrayOutputStream();
//字节数组输出流
ObjectOutputStream oo = new ObjectOutputStream(bo);
//对象输出流
oo.writeObject(obj); //输出对象
buffer = bo.toByteArray(); //对象序列化
}catch(IOException ex){}
return buffer;
}
/**
* 字节数组转化为Object对象形式,实现对象反序列化
* @param buffer 字节数组
* @return Object 对象
*/
public static Object ByteToObject(byte[] buffer){
Object obj = null;
try{
ByteArrayInputStream bi = new ByteArrayInputStream(buffer); //字节数组输入流
ObjectInputStream oi = new ObjectInputStream(bi); //对象输入流
obj = oi.readObject(); //转为对象
}catch(IOException | ClassNotFoundException ex){}
return obj;
}
}
六. QQServer包结构:
图表 19 QQServer包结构
1. com.example包
A. ReceiveMessage.java: 服务器处理消息的线程类
在该类中,新增了安全登录的验证功能,支持数据库验证方法,其主要代码如下:
if(msg.getType().equalsIgnoreCase("M_LOGIN")){ //M_LOGIN 消息
Message backMsg=new Message();
//先更新服务器在线用户总数
parentUI.userTotal.setText(String.valueOf(userList.size()));
Member bean=new Member();
bean.setId(Integer.parseInt(userId));
bean.setPassword(msg.getPassword());
if(!MemberManager.userLogin(bean)){ //登录失败的情况
//登录不成功的情况
backMsg.setType("M_FAILURE");
byte[]buf=Translate.ObjectToByte(backMsg);
DatagramPacket backPacket=new DatagramPacket(
buf,buf.length,packet.getAddress(),packet.getPort());
//向登录用户发送的报文
serverSocket.send(backPacket); //发送
}else{ //登录成功的情况
}
}
B. RecvFile.java:接收文件的后台线程类
接收文件与验证签名的逻辑步骤与发送文件与数字签名的逻辑步骤大致是一一对应的。其主要逻辑如下:
- 接收文件名、文件长度;
- 接收文件内容,存储为外部文件;
- 接收加密的数字签名;
- 接收加密的密钥;
- 根据文件输出流重新计算消息摘要。
数字签名的工作原理如下图所示:
图表 20 带数字签名的文件收发逻辑对照
C. ServerUI.java: 服务器界面
图表 21 QQ聊天室服务器端主界面
ServerUI主要的成员方法如下:
private void btnStartActionPerformed(java.awt.event.ActionEvent evt):
“启动服务器”按钮监听器,实现服务器在指定端口侦听功能;
2. com.example.db包
A. DBTest.java:测试程序
在DBTest中,一一调用和测试MemberManager的各种方法;一方面可以检验数据库访问的正确性,另一方面向数据库中写入一些新数据。
执行该测试程序后,Member表的内容如下:
图表 22 Member表的数据内容
B. DBUtils.java:连接数据库的公共类
在DBUtils中定义了连接数据库的方法,其中主要代码如下:
package com.example.db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBUtils {
//mysql数据库Url
private static final String DBURL="jdbc:mysql://localhost:3306/qqdb?serverTimezone=UTC&&useUnicode=true&&characterEncoding=utf8&&useSSL=false";
private static final String USERNAME="root"; //mysql数据库用户名
private static final String PASSWORD="PASSWORD"; //mysql登录密码
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(DBURL, USERNAME, PASSWORD);
}//end getConnection
}//end class
3. com.example.db.beans包
Member.java:与QQDB数据库的Member数据表对应的Java实体类。
用数据表的名称命名实体类,在该类中,各属性的定义与数据表Member中的字段一一对应,保持属性名与字段名相同,数据类型一致。
Member类定义如下:
public class Member { //定义实体类,属性名与字段名相同,类型也相同
private int id; //对应数据表中的id
private String name; //对应数据表中的name
private String password; //对应数据表中的password
private String email; //对应数据表中的email
private Timestamp time; //对应数据表中的time
private String headImage;//对应数据表中的headImage
}
4. com.example.db.tables包
MemberManager.java:与Member对应的管理类。
该类主要实现针对Member数据表的增、删、改、查等基本操作,其主要实现方法有:
displayAllRows():返回表中所有记录;
getRowById(int id):根据id查找记录;
registerUser(Member bean):注册用户;
userLogin(Member bean):用户登录;
updateUser(Member bean):修改用户;
deleteUser(int id):删除用户。
5. com.example.keystore包
A. server.keystore:该库文件包含客户机与服务器的私钥与公钥。
B. tserver.keystore:该库文件包含客户机的公钥。
6. com.example.security安全包
Cryptography.java:安全类,定义了项目中用到的加密解密和生成密钥的方法。
(主要代码与QQClient客户机中的相同。)
7. com.example.util包
A. Message.java: 消息类
B. Translate.java: 转换类
(这里的Message消息类与Translate转换类,与客户机中的定义是相同的)
C. User.java: 用户类
User.java代码如下:
package com.example.util;
import java.net.DatagramPacket;
/**
* @author Jacky
*/
public class User {
private String userId = null; //用户id
private String password = null;//密码
private DatagramPacket packet; //报文
public String getUserId() {
return userId; }
public void setUserId(String userId) {
this.userId = userId;
}
public String getPassword() {
return password; }
public void setPassword(String password) {
this.password = password;
}
public DatagramPacket getPacket() {
return packet; }
public void setPacket(DatagramPacket packet) {
this.packet = packet;
}
}
8. 库
新增了项目需要的库,为Java程序提供相关的API支持,在本项目中,添加“MySQL JDBC 驱动程序”,如下图所示:
图表 23 添加驱动程序
七. 其他功能实现运行结果:
1. 注册功能:
输入正确信息后显示“注册成功,请进行登录...”,如下图:
图表 24 用户注册
点击确定后跳转到登录页面:
图表 25 注册成功首次登录 图表 26 正常登录成功
2. 在线用户显示功能:
图表 27 服务端用户显示 图表 28 客户端用户显示
当有用户退出聊天室时,在线用户总数与在线用户列表都会同步更新,如图:
图表 29 服务端用户退出 图表 30 客户端用户退出
3. 清除聊天记录功能:
图表 31 清除前 图表 32 清除后
图表 33 用户222与用户111的私聊窗口 图表 34 用户111与用户222的私聊窗口
5. 登录错误提示:
当聊天室达到最大用户容量30时,客户机登录出错,错误提示如下:
图表 35 聊天室达最大容量提示
当用户登录时,用户名或密码为空时错误提示如下:
图表 36 登录错误提示
6. 注册错误提示:
进行注册账号时,如两次输入的密码不一致,提示如下图:
图表 37 注册失败提示
进行注册账号时,如输入的账号是数据库里已存在的用户名时,提示如下图:
图表 38 注册账号已存在提示
本次项目到这里就结束啦 ~~~ 关于项目中的一些具体细节,可以看看代码中的注释或者评论区问我都可以,欢迎讨论。
不足之处,欢迎各位大佬指正。