简单网络聊天程序设计(JAVA+Eclipse+JFrame)

一、已实现实现功能

1.用户的注册、登录、退出功能(只能由服务器端来操作数据库来添加和查找用户,用户没有直接操作数据库的权限)

2.由服务器转发用户发来的用户状态信息、私聊信息,公聊信息,请求发送文件信息、文件发送响应信息

3.服务器转发文件发送信息和文件响应信息,然后两个客户端直接建立socket连接来发送文件,用户可以选择是否接收文件

4.服务器端可以从数据库删除已注册的用户,并给其他用户发送用户下线信息,已删除的用户不能再发送信息

二、程序用到的主要组件,及其功能实现
    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<String> onlineUserDlm = new DefaultListModel<String>();
        listOnlineUsers = new JList<String>(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 + "成功");
						}
					}
				}
			}
		});
四、代码最终实现效果
    1.客户端最终呈现结果:

    

    2.注册界面最终呈现效果:

    

    3.服务器端最终呈现结果:

    


猜你喜欢

转载自blog.csdn.net/wmrem/article/details/79904251