ChannelGroup 是 Netty 封装的线程安全容器,提供广播、批量关闭、自动清理失效连接等核心能力;直接用 ConcurrentHashMap 或 synchronizedSet 会导致关闭未移除、并发异常、广播逻辑不健壮等问题。

ChannelGroup 是什么,为什么不能直接用普通 Set
ChannelGroup 不是简单的 Set<channel></channel>,它是 Netty 封装好的线程安全容器,自带广播、批量关闭、自动清理失效连接等能力。你如果自己用 ConcurrentHashMap 或 Collections.synchronizedSet 存 Channel,会立刻掉进几个坑:
- Channel 关闭后没自动移出集合,下次
writeAndFlush()会抛java.nio.channels.ClosedChannelException - 并发遍历 + 写操作时容易触发
ConcurrentModificationException - 广播逻辑得自己循环调用
channel.writeAndFlush(),漏掉异常处理就静默失败
而 ChannelGroup 的 broadcast() 方法内部已做 try-catch、跳过 inactive channel、异步提交到对应 EventLoop —— 这些不是“锦上添花”,是长连接服务的生存底线。
怎么初始化和管理 ChannelGroup 才不丢连接
必须用 GlobalEventExecutor.INSTANCE 构造,否则在 Channel 关闭时可能因 EventLoop 不一致导致内存泄漏或 NPE:
private static final ChannelGroup CHANNEL_GROUP =
new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
然后在 ChannelInboundHandlerAdapter 的生命周期方法里严格绑定/解绑:
-
channelActive():调用CHANNEL_GROUP.add(ctx.channel()) -
exceptionCaught():先ctx.close(),别忘了ctx.channel().closeFuture().addListener(...)保证清理 -
channelInactive():Netty 会自动从ChannelGroup移除,但建议加日志确认
别在 handlerRemoved() 里手动 remove() —— 这个回调可能在 channel 已经断开之后才触发,反而干扰自动清理逻辑。
广播消息时 writeAndFlush() 和 broadcast() 到底选哪个
直接用 CHANNEL_GROUP.writeAndFlush(msg),别自己写 for 循环;但要注意:它只发消息,不触发编码器(比如 StringEncoder)—— 如果 pipeline 里有 StringEncoder,必须确保 msg 是 String 类型;如果是 ByteBuf 或自定义对象,得提前 encode 好。
- 错例:
CHANNEL_GROUP.writeAndFlush(new UserMessage("hi"))→ 报java.lang.UnsupportedOperationException: unsupported message type - 对例:
CHANNEL_GROUP.writeAndFlush("hi")或CHANNEL_GROUP.writeAndFlush(Unpooled.copiedBuffer("hi", UTF_8))
另外,broadcast() 是旧版 API,4.1+ 推荐统一走 writeAndFlush(),语义更清晰,也方便后续加过滤逻辑(比如按属性筛选 channel)。
用户级精准推送比全局广播还容易出错
真要推给某个人,靠 ConcurrentHashMap<string channel></string> 存 user-id → channel 映射没问题,但关键在“什么时候存、什么时候删”:
- 存:必须在 WebSocket 握手完成、收到第一个业务消息(含 user-id)后再 put,不能在
channelActive()就存 —— 那时还没认证,ID 可能为空或伪造 - 删:不能只靠
channelInactive(),得监听channel.closeFuture(),因为有些连接是服务端主动踢出的 - 查:用
userChannelMap.get(userId)后,务必检查channel.isActive() && channel.isWritable(),否则可能发到半路卡死
最常被忽略的一点:多个客户端用同一个 user-id 登录(比如手机+网页同时在线),你只保留最后一个 channel,前一个就永远收不到消息了 —— 这种场景需要改用 ConcurrentHashMap<string set>></string>,但随之而来的是广播性能下降和内存占用上升,得权衡。










