You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在基于JFrame的服务端/客户端程序中使用Thread实现多客户端通信

现有代码核心问题

  • 服务端ServerHandler仅调用一次serverSocket.accept(),接收第一个客户端后不再处理新连接请求,无法支持多客户端接入
  • 服务端输入输出流定义为静态全局变量,新客户端连接后会覆盖旧客户端的流,导致仅能和最后接入的客户端通信
  • 缺失消息广播机制:服务端收到单个客户端的消息后,需要转发给所有已连接客户端,才能实现多客户端互通
  • Swing组件直接在子线程更新,不符合Swing单线程模型,易出现界面卡顿、渲染异常问题
  • 客户端连接地址127.8.8.1大概率为笔误,本地测试建议改为127.0.0.1

修复方案

服务端修改逻辑

  1. 在ServerHandler的run方法中添加死循环,持续调用accept()接收新客户端,每接入一个新客户端就启动对应ClientHandler线程
  2. 新增线程安全的集合,存储所有已连接客户端的DataOutputStream实例,用于消息广播
  3. ClientHandler中的输入输出流改为成员变量,不再使用全局静态变量避免覆盖
  4. 收到客户端消息后遍历输出流集合,将消息转发给所有客户端
  5. 更新Swing的chatArea时通过SwingUtilities.invokeLater切换到EDT线程操作
  6. 客户端断开连接时,将对应输出流从集合中移除,避免广播时报错

客户端修改逻辑

无需大改,仅需保证界面更新走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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.18 06:36:09