Java NIO聊天室服务器通过Selector单线程管理多连接:先创建非阻塞ServerSocketChannel并注册OP_ACCEPT;主循环调用select()监听事件,分别处理新连接、读消息(含粘包解析)和写响应;维护客户端集合实现广播,并在read返回-1时清理断开连接。

用 Java NIO 实现一个简单的聊天室服务器,核心是用 Selector 管理多个客户端连接,避免为每个连接开线程,做到单线程(或少量线程)处理大量并发连接。关键在于非阻塞 I/O、事件驱动和缓冲区管理。
1. 初始化服务端 Channel 和 Selector
创建 ServerSocketChannel,设为非阻塞,并注册到 Selector,监听 OP_ACCEPT 事件:
ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false); serverChannel.bind(new InetSocketAddress(8080)); Selector selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT);
注意:必须先调用 configureBlocking(false) 才能注册到 Selector。
2. 主循环:轮询就绪事件并分发处理
在 while 循环中调用 selector.select() 阻塞等待事件,再遍历 selectedKeys() 处理不同类型的就绪键:
-
OP_ACCEPT:接受新连接,将新
SocketChannel设为非阻塞,注册OP_READ -
OP_READ:从客户端读取数据。需用
ByteBuffer缓存,注意检查read()返回值(-1 表示断开,0 表示无数据) - OP_WRITE:仅在写缓冲区满、需要继续发送时才临时注册;通常写操作直接尝试,失败再注册写事件
每次处理完 key 后记得调用 key.remove() 或使用 Iterator.remove(),否则下次 select 会重复返回。
3. 实现广播逻辑(聊天室核心)
维护一个 Set 存储所有活跃客户端(线程安全,可用 Collections.synchronizedSet() 或 ConcurrentHashMap.newKeySet())。收到某客户端消息后:
- 解析出文本(例如按换行符
\n切分) - 遍历所有 channel,跳过发送者自身
- 对每个目标 channel 调用
channel.write(buffer);若返回值小于 buffer 剩余字节,说明未写完,需保留 buffer 并注册OP_WRITE,待下次可写时继续
建议把消息封装成统一格式,如 "[user1]: hello\n",便于解析和显示。
4. 客户端断开的检测与清理
NIO 不会自动感知断开,需在 read() 返回 -1 时主动关闭 channel,并从客户端集合中移除。同时调用 key.cancel() 和 channel.close()。不要只关 channel 而不 cancel key,否则 selector 可能报异常。
也可以加简单心跳(如客户端每 30 秒发一次 PING),服务端超时未收到则清理连接。
不复杂但容易忽略细节:缓冲区 flip/clear 时机、字符串编码(统一用 UTF-8)、粘包/半包处理(至少按行解析)、异常时的资源清理。写完后可用 telnet 或简单 Netcat 客户端测试:`telnet localhost 8080`。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











