如何在基于JFrame的服务端/客户端程序中使用Thread实现多客户端通信
现有代码核心问题
- 服务端
ServerHandler仅调用一次serverSocket.accept(),接收第一个客户端后不再处理新连接请求,无法支持多客户端接入 - 服务端输入输出流定义为静态全局变量,新客户端连接后会覆盖旧客户端的流,导致仅能和最后接入的客户端通信
- 缺失消息广播机制:服务端收到单个客户端的消息后,需要转发给所有已连接客户端,才能实现多客户端互通
- Swing组件直接在子线程更新,不符合Swing单线程模型,易出现界面卡顿、渲染异常问题
- 客户端连接地址
127.8.8.1大概率为笔误,本地测试建议改为127.0.0.1
修复方案
服务端修改逻辑
- 在
ServerHandler的run方法中添加死循环,持续调用accept()接收新客户端,每接入一个新客户端就启动对应ClientHandler线程 - 新增线程安全的集合,存储所有已连接客户端的
DataOutputStream实例,用于消息广播 ClientHandler中的输入输出流改为成员变量,不再使用全局静态变量避免覆盖- 收到客户端消息后遍历输出流集合,将消息转发给所有客户端
- 更新Swing的
chatArea时通过SwingUtilities.invokeLater切换到EDT线程操作 - 客户端断开连接时,将对应输出流从集合中移除,避免广播时报错
客户端修改逻辑
无需大改,仅需保证界面更新走EDT线程,发送的消息同步显示在本地聊天框即可
修复后完整代码
服务端 ServerGui1
package server; import java.io.DataInputStream; import java.io.DataOutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.CopyOnWriteArrayList; import javax.swing.SwingUtilities; public class ServerGui1 extends javax.swing.JFrame { static ServerSocket serverSocket; // 线程安全集合,存储所有客户端的输出流 static CopyOnWriteArrayList<DataOutputStream> clientOutputs = new CopyOnWriteArrayList<>(); public ServerGui1() { initComponents(); } private void sendActionPerformed(java.awt.event.ActionEvent evt) { try{ String sendMsg = msgText.getText().trim(); // 服务端发送的消息广播给所有客户端 broadcastMessage("Server: " + sendMsg); msgText.setText(""); } catch (Exception e) { e.printStackTrace(); } } // 广播消息给所有客户端 private static void broadcastMessage(String msg) { for (DataOutputStream out : clientOutputs) { try { out.writeUTF(msg); } catch (Exception e) { // 发送失败说明客户端已断开,移除对应流 clientOutputs.remove(out); } } // 同步更新服务端聊天界面 SwingUtilities.invokeLater(() -> { chatArea.setText(chatArea.getText().trim() + "\n" + msg); }); } public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { new ServerGui1().setVisible(true); start(); } }); } public static void start() { new Thread(new ServerHandler()).start(); } private static class ServerHandler implements Runnable { @Override public void run() { try { serverSocket = new ServerSocket(2021); // 死循环持续接收新客户端连接 while (true) { Socket clientSocket = serverSocket.accept(); DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream()); clientOutputs.add(out); new Thread(new ClientHandler(clientSocket)).start(); } } catch (Exception e) { e.printStackTrace(); } } } private static class ClientHandler implements Runnable { private Socket clientSocket; private DataInputStream inputFromClient; private DataOutputStream outPutToClient; private String msg = ""; public ClientHandler(Socket clientSocket) { this.clientSocket = clientSocket; } @Override public void run() { try { inputFromClient = new DataInputStream(clientSocket.getInputStream()); outPutToClient = new DataOutputStream(clientSocket.getOutputStream()); while (!msg.equalsIgnoreCase("quit")) { msg = inputFromClient.readUTF(); // 收到客户端消息后广播给所有接入端 broadcastMessage("Client(" + clientSocket.getInetAddress().getHostAddress() + "): " + msg); } } catch (Exception e) { e.printStackTrace(); } finally { // 客户端断开后清理资源 clientOutputs.remove(outPutToClient); try { clientSocket.close(); } catch (Exception e) {} } } } // Variables declaration - do not modify private static javax.swing.JTextArea chatArea; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JTextField msgText; private javax.swing.JButton send; // End of variables declaration }
客户端 ClientGui1
package client; import java.io.DataInputStream; import java.io.DataOutputStream; import java.net.Socket; import javax.swing.SwingUtilities; public class ClientGui1 extends javax.swing.JFrame { static Socket clientSocket; static DataOutputStream outputToServer; static DataInputStream inputFromServer; public ClientGui1() { initComponents(); } private void sendActionPerformed(java.awt.event.ActionEvent evt) { try{ String sendMsg = msgText.getText().trim(); outputToServer.writeUTF(sendMsg); // 本地发送的消息同步显示在聊天框 SwingUtilities.invokeLater(() -> { chatArea.setText(chatArea.getText().trim() + "\n我: " + sendMsg); }); msgText.setText(""); } catch (Exception e) { e.printStackTrace(); } } public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { new ClientGui1().setVisible(true); start(); } }); } public static void start() { try { // 本地测试默认连接127.0.0.1,远程部署修改为对应服务端IP即可 clientSocket = new Socket("127.0.0.1", 2021); outputToServer = new DataOutputStream(clientSocket.getOutputStream()); new Thread(new Listener()).start(); } catch (Exception e) { e.printStackTrace(); } } private static class Listener implements Runnable { @Override public void run() { try { inputFromServer = new DataInputStream(clientSocket.getInputStream()); String msg = ""; while (!"quit".equalsIgnoreCase(msg)){ msg = inputFromServer.readUTF(); String finalMsg = msg; SwingUtilities.invokeLater(() -> { chatArea.setText(chatArea.getText().trim() + "\n" + finalMsg); }); } } catch (Exception e) { e.printStackTrace(); } } } // Variables declaration - do not modify private static javax.swing.JTextArea chatArea; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JTextField msgText; private javax.swing.JButton send; // End of variables declaration }
修改后启动服务端,再启动多个客户端,所有接入端即可正常互通消息,任意一端发送的内容都会同步到所有客户端和服务端的聊天界面。
内容的提问来源于stack exchange,提问作者RamiB
相关产品推荐
相关产品推荐

