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

Socket多线程聊天服务器问题:客户端无法互相通信

嘿,我帮你搞定这个问题!你现在的核心问题是服务器没有实现消息的广播转发逻辑——当一个客户端发送消息过来时,服务器只处理了和这个客户端的单向交互,没有把消息同步给列表里的其他在线客户端。下面我给你一套完整的修复方案,包括修正后的服务器代码和客户端示例:

核心问题分析

你原来的代码应该是只维护了客户端连接的列表,但没有在收到消息时遍历列表,把消息转发给除发送者之外的其他客户端。另外,普通的ArrayList在多线程环境下会有并发修改异常,得用线程安全的集合来管理客户端连接。

修正后的聊天服务器代码

1. ChatServer 主类(负责监听连接、管理客户端)

package Threads.tcp;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.CopyOnWriteArrayList;

public class ChatServer {
    private static final int PORT = 10023;
    // 用CopyOnWriteArrayList保证多线程下的操作安全,避免并发修改异常
    private static final CopyOnWriteArrayList<ClientHandler> clientHandlers = new CopyOnWriteArrayList<>();

    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            System.out.println("聊天服务器已启动,监听端口:" + PORT);

            // 持续监听客户端连接请求
            while (true) {
                Socket clientSocket = serverSocket.accept();
                System.out.println("新客户端上线:" + clientSocket.getInetAddress().getHostAddress());

                // 为每个客户端创建独立的处理线程
                ClientHandler handler = new ClientHandler(clientSocket);
                clientHandlers.add(handler);
                new Thread(handler).start();
            }
        } catch (IOException e) {
            System.err.println("服务器启动失败:" + e.getMessage());
            e.printStackTrace();
        }
    }

    // 广播消息给所有在线客户端(排除消息发送者)
    public static void broadcastMessage(String message, ClientHandler sender) {
        for (ClientHandler handler : clientHandlers) {
            if (handler != sender) {
                handler.sendMessage(message);
            }
        }
    }

    // 移除断开连接的客户端
    public static void removeClient(ClientHandler handler) {
        clientHandlers.remove(handler);
        System.out.println("客户端下线,当前在线人数:" + clientHandlers.size());
    }
}

2. ClientHandler 类(负责单个客户端的消息读写)

这个类会单独跑在一个线程里,处理和对应客户端的通信,同时调用服务器的广播方法转发消息:

package Threads.tcp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;

public class ClientHandler implements Runnable {
    private final Socket clientSocket;
    private PrintWriter messageWriter;
    private BufferedReader messageReader;

    public ClientHandler(Socket socket) {
        this.clientSocket = socket;
    }

    @Override
    public void run() {
        try {
            // 初始化输入输出流,用于和客户端收发消息
            messageReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            messageWriter = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream()), true);

            String receivedMessage;
            // 持续读取客户端发送的消息
            while ((receivedMessage = messageReader.readLine()) != null) {
                System.out.println("收到消息:" + receivedMessage);
                // 调用服务器的广播方法,把消息转发给其他客户端
                ChatServer.broadcastMessage(receivedMessage, this);
            }
        } catch (IOException e) {
            System.out.println("与客户端通信中断:" + e.getMessage());
        } finally {
            // 关闭资源并从服务器列表中移除当前客户端
            try {
                messageReader.close();
                messageWriter.close();
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            ChatServer.removeClient(this);
        }
    }

    // 发送消息给当前客户端
    public void sendMessage(String message) {
        messageWriter.println(message);
    }
}

配套的客户端代码(解决客户端接收阻塞问题)

客户端需要单独开一个线程处理服务器发来的消息,不然会因为等待输入而阻塞接收逻辑:

package Threads.tcp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class ChatClient {
    private static final String SERVER_HOST = "localhost";
    private static final int SERVER_PORT = 10023;

    public static void main(String[] args) {
        try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {
            System.out.println("成功连接到聊天服务器!");

            // 启动独立线程接收服务器转发的消息
            new Thread(() -> {
                try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
                    String serverMessage;
                    while ((serverMessage = in.readLine()) != null) {
                        // 收到消息时打印,同时提示用户继续输入
                        System.out.println("\n[其他客户端]:" + serverMessage);
                        System.out.print("请输入你的消息:");
                    }
                } catch (IOException e) {
                    System.out.println("\n与服务器断开连接");
                }
            }).start();

            // 处理用户输入,发送消息到服务器
            PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
            Scanner scanner = new Scanner(System.in);
            while (true) {
                System.out.print("请输入你的消息:");
                String userInput = scanner.nextLine();
                out.println(userInput);
            }
        } catch (IOException e) {
            System.err.println("无法连接到服务器:" + e.getMessage());
            e.printStackTrace();
        }
    }
}

关键注意点

  • 线程安全集合:用CopyOnWriteArrayList管理客户端,避免多线程添加/移除客户端时出现并发修改异常。
  • 独立线程处理客户端:每个客户端连接对应一个线程,服务器可以同时处理多个客户端的请求,不会阻塞。
  • 广播逻辑:broadcastMessage方法确保消息只发给除发送者外的其他客户端,避免自己收到自己发的消息。
  • 资源清理:客户端断开时及时关闭流和Socket,并从服务器列表中移除,避免无效连接占用资源。

内容的提问来源于stack exchange,提问作者user9064113

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:11:27