直接new Socket()无法实现多人聊天室,因其仅支持点对点连接;聊天室需ServerSocket监听并为每个客户端创建独立Socket,配合多线程/IO模型、共享客户端列表、双线程收发、UTF-8统一编码及正确关闭流程。

为什么直接 new Socket() 无法实现多人聊天室
单个 Socket 只能连接一个对端,而聊天室本质是“一对多”通信:一个客户端发消息,所有在线用户都要收到。如果只靠客户端主动连服务端再发消息,服务端必须同时维护多个连接、广播消息、处理断线重连——这些都不是 Socket 构造函数本身能解决的。
真正要做的,是让服务端用 ServerSocket 监听端口,为每个接入的客户端分配独立线程(或使用 NIO),并维护一个共享的客户端列表。客户端仍用 Socket 连接,但必须持续读取服务端推送的消息,不能发完就关。
- 常见错误:客户端用
socket.getOutputStream().write()发完消息就调用socket.close(),导致后续收不到广播 - 服务端必须避免在主线程里阻塞读取某一个客户端,否则其他连接会被挂起
- 多个线程同时操作客户端列表时,需用
Collections.synchronizedList()或CopyOnWriteArrayList
ServerSocket.accept() 必须放在循环里,且不能在子线程中调用
accept() 是阻塞方法,每次返回一个新的 Socket 实例,代表一个具体客户端连接。它不能只调用一次——否则只能服务第一个用户。
典型结构是:
立即学习“Java免费学习笔记(深入)”;
ServerSocket server = new ServerSocket(8080);
while (!shutdown) {
Socket client = server.accept(); // 阻塞直到有新连接
new Thread(new ClientHandler(client)).start();
}
- 如果把
accept()写在子线程里,会导致新连接无法被及时接收,甚至丢连接 - 不要在
ClientHandler的run()方法开头就调用accept()——那是服务端的事,客户端线程只负责和已建立的那个Socket通信 - 注意设置超时:
server.setSoTimeout(30000)可防止accept()永久阻塞(配合中断逻辑)
客户端如何做到“一边发、一边收”,避免阻塞在 readLine()
Java 的 BufferedReader.readLine() 是阻塞调用,如果只用一个线程先后执行“发消息”和“收消息”,那发完就得等服务器回,根本做不到实时收别人的消息。
必须拆成两个线程(或用 ExecutorService):
- 输入线程:从
System.in读用户输入,写入socket.getOutputStream() - 接收线程:从
socket.getInputStream()包装成BufferedReader,循环readLine()并打印
关键细节:
- 别用
PrintWriter.println()单独发消息而不 flush——默认不自动 flush,服务端会一直等换行符后的数据 - 服务端接收时也要用
BufferedReader.readLine(),确保按行边界解析,否则粘包会导致消息错乱 - 关闭时优先关闭输出流(
socket.shutdownOutput()),让对方readLine()返回null,再关整个 socket
中文消息乱码的根本原因和最简修复方式
默认 InputStreamReader 和 OutputStreamWriter 使用平台编码(Windows 是 GBK,Linux/macOS 是 UTF-8),只要两端不一致,中文必乱码。
唯一可靠做法:显式指定字符集,且两端严格统一。
- 服务端创建 reader/writer 时:
new InputStreamReader(socket.getInputStream(), "UTF-8")new OutputStreamWriter(socket.getOutputStream(), "UTF-8") - 客户端同理,不能依赖默认构造函数
- 如果用
Scanner读控制台输入,也要指定:new Scanner(System.in, "UTF-8") - IDE 运行配置里也要设 VM option:
-Dfile.encoding=UTF-8,否则System.out输出可能仍是乱码
聊天室里一旦出现半个汉字或异常退出,第一反应就该查这四点:服务端读、服务端写、客户端读、客户端写——是否全用了 UTF-8。









