




最简Java聊天室基于ServerSocket/Socket阻塞I/O与多线程:服务端accept后立即启新线程处理客户端,用BufferedReader/PrintWriter收发文本(PrintWriter需auto-flush),ConcurrentHashMap管理在线客户端PrintWriter,客户端需双线程分别收发消息,并妥善处理连接中断与资源关闭。
ServerSocket 和 Socket 搭建基础服务端-客户端通信Java 聊天室最简模型不依赖任何框架,核心就是阻塞式 I/O 配合多线程。服务端靠 ServerSocket 监听端口,每个新连接由独立线程处理;客户端用 Socket 连接并收发字符串。
关键点在于:不能让一个客户端阻塞整个服务端。所以 accept() 后必须立刻丢给新线程,而不是在主线程里读写。
ServerSocket server = new ServerSocket(8080);
while (true) {
Socket client = server.accept(); // 阻塞直到有连接
new Thread(() -> handleClient(client)).start(); // 立刻交出去
}
常见错误是把 handleClient 写成同步阻塞读(比如只调一次 readLine()),结果一个用户发一条消息后线程就卡死,后续消息收不到。
BufferedReader + PrintWriter 处理文本消息的坑聊天室传的是纯文本,用 BufferedReader.readLine() 和 PrintWriter.println() 最直接。但必须注意三件事:
PrintWriter 构造时要传 true 开启自动 flush,否则消息卡在缓冲区不出去readLine() 遇到流关闭或异常会返回 null,不是空字符串,别用 == "" 判空BufferedReader / PrintWriter,输入输出不能混用同一个流示例片段(服务端处理单个客户端):
void handleClient(Socket s) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter out = new PrintWriter(s.getOutputStream(), true); // 注意 true
String msg;
while ((msg = in.readLine()) != null) {
System.out.println("收到:" + msg);
out.println("[已接收] " + msg); // 广播逻辑这里先省略
}
s.close();
}
ConcurrentHashMap 管理在线客户端列表要实现“群聊”,服务端得记住所有活跃的 PrintWriter(即每个客户端的输出通道)。不能用 HashMap —— 多线程并发遍历时会抛 ConcurrentModificationException。
ConcurrentHashMap 是安全选择,但注意它不保证迭代过程中的强一致性(比如遍历时有人退出,可能漏发或重复发,对简单聊天室可接受)。
典型用法:
s.getRemoteSocketAddress())PrintWriter,别存 Socket 或 BufferedReader,避免资源误关map.values().forEach(out -> out.println(...))
退出清理必须做:在 handleClient 的 finally 块里从 map 中移除对应项,并显式 close() 流。
如果客户端只用一个线程,要么只能发、要么只能收,交互卡顿。标准解法是:
PrintWriter 发送BufferedReader.readLine() 持续监听服务端消息并打印容易忽略的是:当服务端断开时,客户端的 readLine() 会返回 null,此时收消息线程应自然退出,同时通知主线程停止发送(比如设个 volatile boolean connected = false 标志位)。
没有心跳机制时,网络闪断无法及时感知,只能靠下一次读/写操作触发异常 —— 这是简单模型的固有限制。
真正难的不是写通路,而是异常分支:连接中断、流关闭、线程中断、资源泄漏。每个 Socket 对应的输入输出流,只要打开就必须在明确时机关闭,且不能重复关 —
