在Java里如何开发简单聊天室模型_Java多线程初级实战解析

最简Java聊天室基于ServerSocket/Socket阻塞I/O与多线程:服务端accept后立即启新线程处理客户端,用BufferedReader/PrintWriter收发文本(PrintWriter需auto-flush),ConcurrentHashMap管理在线客户端PrintWriter,客户端需双线程分别收发消息,并妥善处理连接中断与资源关闭。

ServerSocketSocket 搭建基础服务端-客户端通信

Java 聊天室最简模型不依赖任何框架,核心就是阻塞式 I/O 配合多线程。服务端靠 ServerSo

cket 监听端口,每个新连接由独立线程处理;客户端用 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 是安全选择,但注意它不保证迭代过程中的强一致性(比如遍历时有人退出,可能漏发或重复发,对简单聊天室可接受)。

典型用法:

  • 键用自增 ID 或客户端地址(s.getRemoteSocketAddress()
  • 值存 PrintWriter,别存 SocketBufferedReader,避免资源误关
  • 每次发广播前,用 map.values().forEach(out -> out.println(...))

退出清理必须做:在 handleClientfinally 块里从 map 中移除对应项,并显式 close() 流。

客户端也要开两个线程:一收一发

如果客户端只用一个线程,要么只能发、要么只能收,交互卡顿。标准解法是:

  • 主线程负责从控制台读输入,通过 PrintWriter 发送
  • 另起一个线程,用 BufferedReader.readLine() 持续监听服务端消息并打印

容易忽略的是:当服务端断开时,客户端的 readLine() 会返回 null,此时收消息线程应自然退出,同时通知主线程停止发送(比如设个 volatile boolean connected = false 标志位)。

没有心跳机制时,网络闪断无法及时感知,只能靠下一次读/写操作触发异常 —— 这是简单模型的固有限制。

真正难的不是写通路,而是异常分支:连接中断、流关闭、线程中断、资源泄漏。每个 Socket 对应的输入输出流,只要打开就必须在明确时机关闭,且不能重复关 —— 这部分逻辑一旦疏忽,跑几分钟后就会出现“能连不能聊”或者“发消息没反应”的现象。