一、已实现实现功能
1.用户的注册、登录、退出功能(只能由服务器端来操作数据库来添加和查找用户,用户没有直接操作数据库的权限)
2.由服务器转发用户发来的用户状态信息、私聊信息,公聊信息,请求发送文件信息、文件发送响应信息
3.服务器转发文件发送信息和文件响应信息,然后两个客户端直接建立socket连接来发送文件,用户可以选择是否接收文件
4.服务器端可以从数据库删除已注册的用户,并给其他用户发送用户下线信息,已删除的用户不能再发送信息
注:
源代码:https://download.csdn.net/download/wmrem/10827465
二、程序用到的主要组件,及其功能实现
1. JButton-实现用户登录、注册、发送文件和发送聊天信息的功能
双击Button按钮,添加Button的单击触发事件方法,在函数里添加功能实现代码。调用button的setText("button的名称") 设置button的名称,setEnabled(true)可将按钮设为可用状态。
// 将发送文件按钮设为可用状态;将发送消息按钮设为
btnSendMsg.setEnabled(true);
btnSendFile.setEnabled(true);
checkBoxPrivateChat.setEnabled(true);
// 将“登录”按钮设为“退出”按钮
btnLogon.setText("退出");
2. JTextFile-用于输入用户名,聊天信息
JTextFile主要是用setText()方法来获取文本框的值
localUserName = textFieldUserName.getText().trim();// 获取用户名并删除多余空格
3. JPasswordField-用于输入用户密码
localPassword = new String(passwordFieldPwd.getPassword());
4. JTextPane-记录用户的各种操作信息
// 向消息记录文本框中添加一条消息记录
private void addMsgRecord(final String msgRecord, Color msgColor, int fontSize, boolean isItalic,
boolean isUnderline) {
final SimpleAttributeSet attrset = new SimpleAttributeSet();// 简单属性集
StyleConstants.setForeground(attrset, msgColor);// 字体颜色
StyleConstants.setFontSize(attrset, fontSize);
StyleConstants.setUnderline(attrset, isUnderline);// 下划线
StyleConstants.setItalic(attrset, isItalic);
// 界面线程的事件队列,创建一个任务封装为runnable对象,加入队列,依次取,做任务,防止网络信息的丢失;
// 更新界面的操作都要用EventQueue.invokeLater不然会造成:后台线程丢失数据,界面花掉,失去响应
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
// 把文本添加到消息记录文本框中
Document docs = textPaneMsgRecord.getDocument();
try {
docs.insertString(docs.getLength(), msgRecord, attrset);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
});
}
insertComponen(组件)方法可以在JTextPane上添加组件
// 进度条加入消息记录文本框
JProgressBar fileSendProcessBar = new JProgressBar();
EventQueue.invokeLater(new Runnable() {
public void run() {
textPaneMsgRecord.insertComponent(fileSendProcessBar);
}
});
5. JList-显示当前在线用户列表
使用DefaultListModel创建在线用户列表,并添加addListSelectionListener(new ListSelectionListener()事件,当某一个在线用户被选中时,获取用户名,用于私聊与发送文件功能的实现
// 在线用户列表
private DefaultListModel onlineUserDlm = new DefaultListModel();
listOnlineUsers = new JList(onlineUserDlm);
//点击获取在线用户名
listOnlineUsers.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
if (!listOnlineUsers.getValueIsAdjusting()) {// 选中最后点击的人
privateChatDstUser = listOnlineUsers.getSelectedValue();
System.err.println("选中的收信人为" + privateChatDstUser);// 每点击一次输出一次
}
}
});
6. JCheckBox-实现私聊
JCheckBox是多选按钮,该选项被选中时可以实现发送私聊信息的功能。
// 创建聊天消息,收信人为空时为公聊消息
ChatMessage chatMessage = new ChatMessage(localUserName, "", msgContent);
if (checkBoxPrivateChat.isSelected() && privateChatDstUser != null) {// 发送私聊了消息
chatMessage = new ChatMessage(localUserName, privateChatDstUser, msgContent);
System.out.println("私聊收信人:" + privateChatDstUser);
} else if (checkBoxPrivateChat.isSelected() && privateChatDstUser == null) {
JOptionPane.showMessageDialog(Client.this, "请选择选择私聊聊天对象");
return;
}
7.JProgressBar-用进度条条显示文件发送进程
// 进度条并加入消息记录文本框
JProgressBar fileSendProcessBar = new JProgressBar();
EventQueue.invokeLater(new Runnable() {//防止界面花掉
public void run() {
textPaneMsgRecord.insertComponent(fileSendProcessBar);
}
});
addMsgRecord("\r\n", Color.blue, 12, false, false);
fileSendProcessBar.setVisible(true);// 进度条可见
fileSendProcessBar.setStringPainted(true);// 设置进度条上的字符串显示,false则不能显示
fileSendProcessBar.setForeground(SystemColor.activeCaption);
fileSendProcessBar.setString(prcent);//设置进度条上显示的字符串
fileSendProcessBar.setMaximum(100);//进度条的终点
fileSendProcessBar.setMinimum(0);//进度条的起点
fileSendProcessBar.setValue(value);//设置进度条的完成进度
fileSendProcessBar.setString(prcent + " 当前文件接收速度:" + speedStr);// 设置进度条数值
8.JTable-显示在线用户的相关信息
// “在线用户列表ListModel”,用于维护“在线用户列表”中显示的内容
private final DefaultTableModel onlineUsersDtm = new DefaultTableModel(new Object[] { "用户名", "IP地址", "端口", "登陆时间" },
0);
tableOnlineUsers = new JTable(onlineUsersDtm);
tableOnlineUsers.setPreferredSize(new Dimension(100, 270));
tableOnlineUsers.setFillsViewportHeight(true); // 让JTable充满它的容器
tableOnlineUsers.setPreferredSize(new Dimension(100, 270));
// 将用户信息加入到“在线用户”列表中
onlineUsersDtm.addRow(new Object[] { srcUser, currentUserSocket.getInetAddress().getHostAddress(),
currentUserSocket.getPort(), dateFormat.format(new Date()) });
//选中要删除的用户
int rownumber = tableOnlineUsers.getSelectedRow();//获取选中的行号
selecteOnlineUser = (String) tableOnlineUsers.getValueAt(rownumber, 0);//获取该行第1列的值
三、主要功能的实现代码
1.用户注册
JButton buttonRegister = new JButton("\u6CE8\u518C");
buttonRegister.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String password = new String(passwordFieldPwd.getPassword());
String passwordConf = new String(passwordFieldConf.getPassword());
String username = textFieldUsername.getText().trim();
String phonenumber = textFieldPhone_number.getText().trim();
if(!password.equals(passwordConf)) {
JOptionPane.showMessageDialog(Register.this, "两次输入的密码不相同,请重新输入");
passwordFieldPwd.setText("");
passwordFieldConf.setText("");
return;
}
// 注册新用户
if (phonenumber != null && username.length() > 0 && password.length() > 0) {
String regExp = "^1([358][0-9]|4[579]|66|7[0135678]|9[89])[0-9]{8}$";//手机号的正则表达式
Pattern p = Pattern.compile(regExp);
Matcher m = p.matcher(phonenumber);
if (m.find()) {
try {
socket = new Socket("localhost", port);
oos = new ObjectOutputStream(socket.getOutputStream());
ois = new ObjectInputStream(socket.getInputStream());
} catch (UnknownHostException e1) {
JOptionPane.showMessageDialog(Register.this, "网络不通,请检查你的防火墙");// 相对于client窗体中心对齐
e1.printStackTrace();
} catch (IOException e1) {
JOptionPane.showMessageDialog(Register.this, "服务器未启动");
e1.printStackTrace();
}
// 创建注册用户消息对象
RegisterMessage registerMessage = new RegisterMessage(username, username, true);
registerMessage.setUserName(username);
registerMessage.setPassword(password);
registerMessage.setPhone_number(phonenumber);
try {
synchronized (oos) {
oos.writeObject(registerMessage);
oos.flush();// 实时发送
}
} catch (IOException e1) {
JOptionPane.showMessageDialog(Register.this, "网络已断开");
e1.printStackTrace();
}
// 创建并启动“后台监听线程”,监听并处理服务器传来的信息
new Thread(new ListeningHandler()).start();
} else {
JOptionPane.showMessageDialog(Register.this, "请输入有效形式的手机号码");
}
} else {
JOptionPane.showMessageDialog(Register.this, "用户名,密码或手机号为空");
}
}
});
class ListeningHandler implements Runnable {
@Override
public void run() {
try {
while (true) {
// Message msg = (Message) ois.readObject();// 收信息,收到的消息强制类型转换为message
Message msg = null;
synchronized (ois) {
msg = (Message) ois.readObject();
}
if (msg instanceof RegisterMessage) {// 用户状态信息,instanceof判断是否属于类
processRegistMessage((RegisterMessage) msg);
break;
} else {
System.out.println("收到错误信息");
}
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
if (e.toString().endsWith("Connection reset")) {
System.out.println("服务器端退出");
} else {
e.printStackTrace();
}
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void processRegistMessage(RegisterMessage msg) {
boolean regist = msg.isRegist();
if (regist) {
JOptionPane.showMessageDialog(Register.this, "注册成功");
} else {
JOptionPane.showMessageDialog(Register.this, "注册失败,用户 '" + msg.getDstUser() + "' 已存在");
}
}
}
2.发送聊天消息
btnSendMsg = new JButton("\u53D1\u9001\u6D88\u606F");
btnSendMsg.addActionListener(new ActionListener() {// 发送消息
public void actionPerformed(ActionEvent e) {
String msgContent = textFieldMsgToSend.getText();
// String dstUser = listOnlineUsers.getSelectedValue();
if (msgContent.length() > 0) {
// 创建聊天消息,收信人为空时为公聊消息
ChatMessage chatMessage = new ChatMessage(localUserName, "", msgContent);
if (checkBoxPrivateChat.isSelected() && privateChatDstUser != null) {// 发送私聊了消息
chatMessage = new ChatMessage(localUserName, privateChatDstUser, msgContent);
System.out.println("私聊收信人:" + privateChatDstUser);
} else if (checkBoxPrivateChat.isSelected() && privateChatDstUser == null) {
JOptionPane.showMessageDialog(Client.this, "请选择选择私聊聊天对象");
return;
}
try {
synchronized (oos) {
oos.writeObject(chatMessage);
oos.flush();
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
});
3.处理请求发送文件信息与接收文件响应信息用于接收和发送文件
class FileSendMessageHandler implements Runnable {
private FileSengMessage fileSengMessage;
public FileSendMessageHandler(FileSengMessage fileSengMessage) {
this.fileSengMessage = fileSengMessage;
}
public void run() {
File file = fileSengMessage.getFile();
String srcUser = fileSengMessage.getSrcUser();// 请求发送文件的发件人
Socket filesocket = null;
if (JOptionPane.showConfirmDialog(Client.this,
"是否同意接收文件\r\n文件名:" + file.getName() + "\r\n文件大小:" + file.length(), "文件接收确认",
JOptionPane.YES_NO_OPTION) == JOptionPane.OK_OPTION) {
JFileChooser fileSave = new JFileChooser("D:");
fileSave.setDialogTitle("保存文件");
fileSave.setSelectedFile(new File(file.getName()));
File fileToSave = fileSave.getSelectedFile();
while (fileSave.showDialog(Client.this, "保存") == JFileChooser.APPROVE_OPTION) {
fileToSave = fileSave.getSelectedFile();
if (fileToSave.exists()) {
if (JOptionPane.showConfirmDialog(Client.this, "是否覆盖", "覆盖确认",
JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION) {// 不覆盖
JOptionPane.showMessageDialog(Client.this,"请重新选择存储位置");
continue;
}
}
if(fileToSave.getPath().equals("C:\\"+fileToSave.getName()) ||
fileToSave.getPath().equals("C:\\Users\\"+fileToSave.getName()) ) {
JOptionPane.showMessageDialog(Client.this,"请不要在c盘的用户需要管理员权限目录存储文件,否则文件将会接收失败");
continue;
}
// 创建进度条并加入消息记录文本框
JProgressBar fileSendProcessBar = new JProgressBar();
try {
ServerSocket serverSocket = new ServerSocket(0);// 打开随机端口
int fileport = serverSocket.getLocalPort();// 获取端口
String ipAddress = InetAddress.getLocalHost().getHostAddress();// 获取IP
FileResponseMessage fileResponseMessage = new FileResponseMessage(localUserName, srcUser, true);
fileResponseMessage.setFile(file);
fileResponseMessage.setPort(fileport);
fileResponseMessage.setIpAddress(ipAddress);
synchronized (oos) {
oos.writeObject(fileResponseMessage);
oos.flush();
}
// 在“消息记录”文本框中用蓝色显示‘发送文件响应信息’
String messageRecord = " 正在接收用户‘" + srcUser + "’发送的文件: " + file.getName();
String msgRecord = dateFormat.format(new Date()) + messageRecord + "\r\n";
addMsgRecord(msgRecord, Color.blue, 12, false, false);
// 进度条并加入消息记录文本框
// JProgressBar fileSendProcessBar = new JProgressBar();
EventQueue.invokeLater(new Runnable() {
public void run() {
textPaneMsgRecord.insertComponent(fileSendProcessBar);
}
});
addMsgRecord("\r\n", Color.blue, 12, false, false);
fileSendProcessBar.setVisible(true);// 进度条可见
fileSendProcessBar.setStringPainted(true);// 设置进度条上的字符串显示,false则不能显示
fileSendProcessBar.setForeground(SystemColor.activeCaption);
System.err.println("-----------" + ipAddress + "----------" + fileport);
filesocket = serverSocket.accept();
InputStream fileInputStream = filesocket.getInputStream();
byte[] buffer = new byte[1024];
FileOutputStream fileOutputStream = new FileOutputStream(fileToSave.getPath());
long fileLength = file.length();// 文件总长度
int nowfilelenth = 0;// 已接收字节数
int read = 0;// 读入字节数
String prcent = 0 + "%";// 传输百分比
fileSendProcessBar.setString(prcent);
fileSendProcessBar.setMaximum(100);
fileSendProcessBar.setMinimum(0);
int value = 0;// 进度条的值
long startTime = System.currentTimeMillis();// 接收开始时的时间
long endTime;// 接收时的时间
String speedStr;// 文件传输的当前速度
while ((read = fileInputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer);
endTime = System.currentTimeMillis();// 接收时的时间
nowfilelenth = nowfilelenth + read;
speedStr = getSpeed(nowfilelenth, startTime, endTime);// 文件接收速度
value = (int) ((nowfilelenth / (fileLength + 0.01)) * 100);
prcent = value + "%";// 文件接收百分比
fileSendProcessBar.setValue(value);
fileSendProcessBar.setString(prcent + " 当前文件接收速度:" + speedStr);// 设置进度条数值
}
fileSendProcessBar.setString("接收完成");
fileOutputStream.close();
fileInputStream.close();
serverSocket.close();
System.out.println("成功接收用户‘" + srcUser + "’发送的文件: " + file.getName());
} catch (IOException e) {
if (e.toString().indexOf("Connection reset") != -1) {
if (filesocket != null) {
try {
filesocket.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
fileSendProcessBar.setValue(0);
fileSendProcessBar.setString("文件接收失败");
System.err.println("用户‘" + srcUser + "’已下线\r\n文件:"+file.getName()+"\r\n接收失败");
JOptionPane.showMessageDialog(Client.this,"用户‘" + srcUser + "’已下线\r\n文件:"+file.getName()+"\r\n接收失败");
}
} else {
e.printStackTrace();
}
} finally {
if (filesocket != null) {
try {
filesocket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return;
}
}
// 拒绝接受文件
// 创建发送文件响应消息
FileResponseMessage fileResponseMessage = new FileResponseMessage(localUserName, srcUser, false);
fileResponseMessage.setFile(file);
// 发送文件响应信息
try {
synchronized (oos) {
oos.writeObject(fileResponseMessage);
oos.flush();
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
System.out.println("拒绝接收用户' " + srcUser + " '发送的文件: " + file.getName());
}
}
class FileResponseMessageHandler implements Runnable {
private FileResponseMessage fileResponseMessage;
public FileResponseMessageHandler(FileResponseMessage fileResponseMessage) {
this.fileResponseMessage = fileResponseMessage;
}
public void run() {
String srcUser = fileResponseMessage.getSrcUser();// 发件人
String fileName = fileResponseMessage.getFile().getName();
if (fileResponseMessage.isRefuseReceiveFile()) {// 拒绝接收文件
// 用蓝色文字将收到消息的时间、发送消息的用户名和消息内容添加到“消息记录”文本框中
final String msgRecord = dateFormat.format(new Date()) + " 用户‘" + srcUser + "’拒绝接收文件:" + fileName
+ "\r\n";
addMsgRecord(msgRecord, Color.blue, 12, false, false);
System.out.println("用户‘" + srcUser + "’拒绝接收文件:" + fileName);
} else if (fileResponseMessage.isAgreeReceiveFile()) {// 同意接收文件
int filePort = fileResponseMessage.getPort();
String fileIpAddress = fileResponseMessage.getIpAddress();
File file = fileResponseMessage.getFile();
Socket fileSocket = null;
// 创建进度条并加入消息记录文本框
JProgressBar fileSendProcessBar = new JProgressBar();
try {
System.err.println("======" + fileIpAddress + "=======" + filePort);
fileSocket = new Socket(fileIpAddress, filePort);
final String msgRecord = dateFormat.format(new Date()) + " 正在向用户‘" + srcUser + "’发送文件:" + fileName
+ "\r\n";
addMsgRecord(msgRecord, Color.blue, 12, false, false);
// 创建进度条并加入消息记录文本框
// JProgressBar fileSendProcessBar = new JProgressBar();
EventQueue.invokeLater(new Runnable() {
public void run() {
textPaneMsgRecord.insertComponent(fileSendProcessBar);
}
});
addMsgRecord("\r\n", Color.blue, 12, false, false);
fileSendProcessBar.setVisible(true);
fileSendProcessBar.setStringPainted(true);// 设置进度条上的字符串显示,false则不能显示
fileSendProcessBar.setForeground(SystemColor.activeCaption);
FileInputStream fileInputStream = new FileInputStream(file);
OutputStream outputStream = fileSocket.getOutputStream();
byte[] buffer = new byte[1024];
long fileLength = file.length();// 文件总长度
int nowfilelenth = 0;// 已接收字节数
int read = 0;// 读入字节数
String prcent = 0 + "%";// 传输百分比
fileSendProcessBar.setString(prcent);
fileSendProcessBar.setMaximum(100);
fileSendProcessBar.setMinimum(0);
int value = 0;// 进度条的值
long startTime = System.currentTimeMillis();// 接收开始时的时间
long endTime;// 发送结束时的时间
String speedStr;// 文件传输的当前速度
while ((read = fileInputStream.read(buffer)) != -1) {
outputStream.write(buffer);
endTime = System.currentTimeMillis();// 发送时的时间
nowfilelenth = nowfilelenth + read;
speedStr = getSpeed(nowfilelenth, startTime, endTime);// 文件接收速度
value = (int) ((nowfilelenth / (fileLength + 0.01)) * 100);
prcent = value + "%";// 文件接收百分比
fileSendProcessBar.setValue(value);
fileSendProcessBar.setString(prcent + " 当前文件发送速度:" + speedStr);// 设置进度条数值
}
fileSendProcessBar.setString("发送完成");
fileInputStream.close();
outputStream.close();
fileSocket.close();
System.out.println("用户‘" + srcUser + "’同意接收文件:" + file.getName() + ",文件发送完成");
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
if (e.toString().indexOf("Connection reset") != -1) {
if (fileSocket != null) {
try {
fileSocket.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
fileSendProcessBar.setValue(0);
fileSendProcessBar.setString("文件发送失败");
System.err.println("用户‘" + srcUser + "’已下线\r\n文件:"+fileName+"\r\n发送失败");
JOptionPane.showMessageDialog(Client.this,"用户‘" + srcUser + "’已下线\r\n文件:"+fileName+"\r\n发送失败");
}
} else {
e.printStackTrace();
}
} finally {
if (fileSocket != null) {
try {
fileSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
4.服务器端踢人
buttonDeleteUser = new JButton("\u5220\u9664\u7528\u6237");
buttonDeleteUser.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
//选中要删除的用户
int rownumber = tableOnlineUsers.getSelectedRow();
selecteOnlineUser = (String) tableOnlineUsers.getValueAt(rownumber, 0);
if (userManager.hasUser(selecteOnlineUser)) {
String password = userManager.getPassword(selecteOnlineUser);
if (password != null) {
// 发送用户下线信息
UserStateMessage msg = new UserStateMessage(selecteOnlineUser, "", false);
System.err.println("-----要删除的用户名" + selecteOnlineUser + " 密码:" + password + "-------");
// 在“在线用户列表”中删除下线用户
userManager.removeUser(selecteOnlineUser);
for (int i = 0; i < onlineUsersDtm.getRowCount(); i++) {
if (onlineUsersDtm.getValueAt(i, 0).equals(selecteOnlineUser)) {
onlineUsersDtm.removeRow(i);
}
}
// 服务器删除用户后,转发用户下线信息
String[] users = userManager.getAllUsers();
for (String user : users) {
if (!msg.getSrcUser().equals(user)) {
try {
ObjectOutputStream oos = userManager.getUserOos(user);
synchronized (oos) {
oos.writeObject(msg);
oos.flush();
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
boolean deleteUser = userDatabase.deleteUser(selecteOnlineUser, password);
if (deleteUser) {
JOptionPane.showMessageDialog(Server.this, "从数据库删除用户" + selecteOnlineUser + "成功");
}
}
}
}
});
if (!msg.getSrcUser().equals(user)) {
try {
ObjectOutputStream oos = userManager.getUserOos(user);
synchronized (oos) {
oos.writeObject(msg);
oos.flush();
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
boolean deleteUser = userDatabase.deleteUser(selecteOnlineUser, password);
if (deleteUser) {
JOptionPane.showMessageDialog(Server.this, "从数据库删除用户" + selecteOnlineUser + "成功");
}
}
}
}
});
四、代码最终实现效果
1.客户端最终呈现结果:
2.注册界面最终呈现效果:
3.服务器端最终呈现结果: