一、Netty 是什么
Netty 是一个基于 Java NIO 封装的 异步事件驱动 网络通信框架。它解决了原生 NIO 编程复杂(Selector、空轮询 Bug、手动拆包粘包处理)的问题,凭借主从 Reactor 线程模型可以用少量线程支撑海量长连接,是构建 IM、推送、网关类服务的标准选择。核心概念:
- EventLoopGroup :线程组。boss 负责接收连接,worker 负责已连接 Channel 的读写事件
- Channel / ChannelPipeline :一条连接及其上的处理链,Handler 按序串联
- Handler :编解码器、业务处理器都挂在 Pipeline 上,分入站/出站方向
- ByteBuf :Netty 的字节容器,用于自定义协议的拆包组包
二、在本项目中的整体用法
项目单独拆了 im-server 模块作为 Netty 长连接网关,与业务服务 im-business 分离。先看全局结构。 
1. 双协议服务器与启动流程
项目用 一套业务代码同时承载两种接入协议 ,全部实现 IMServer 接口:
- WebSocketServer.java — 服务浏览器,
ws://端口 8878,默认开启 - TcpSocketServer.java — 服务桌面/移动客户端,端口 8879,默认关闭( application.yml 中
tcpsocket.enable: false) 两者都通过@ConditionalOnProperty注册为 Spring Bean,由 IMServerGroup.java (实现CommandLineRunner)在应用启动后统一拉起:先用 RedisINCR生成全局自增的serverId(多实例部署时用于消息路由),再逐个调用imServer.start()。
标准的 Netty 服务端启动套路( TcpSocketServer.java#L44-L74 ):
ServerBootstrap+ 主从线程模型:bossGroup只负责 accept 连接,workGroup负责注册在其上 Channel 的读写事件.channel(NioServerSocketChannel.class)指定 NIO 通信方式.childHandler(new ChannelInitializer<>())为每个新连接装配 Pipeline.option(SO_BACKLOG)/.childOption(SO_KEEPALIVE)分别配置主/从线程池的 TCP 参数bootstrap.bind(port).sync()绑定端口;关闭时shutdownGracefully()优雅释放线程组
2. ChannelPipeline:编解码与协议差异
Pipeline 是 Netty 的责任链。两个服务器按各自协议组装不同的 Handler 链,但 末端都收敛到同一个 IMChannelHandler ,并统一产出IMSendInfo (cmd + data)这个中间消息模型。 
编解码要点:
- TCP 侧 ( tcp/endecode ):自定义协议 =
8 字节长度 + JSON。 MessageProtocolEncoder 继承MessageToByteEncoder写出时先写长度再写体;Decoder 继承ReplayingDecoder,先读长度字段再读定长内容,天然解决 TCP 拆包/粘包 问题 - WebSocket 侧 ( ws/endecode ):前面挂了 HTTP 编解码、聚合、分块写、
WebSocketServerProtocolHandler("/im")负责握手升级,帧本身的边界由 ws 协议保证,所以 Decoder 只需把TextWebSocketFrame的文本反序列化成IMSendInfo - 两条链头部都挂了
IdleStateHandler(WS 60s / TCP 120s 读空闲),做连接保活检测
3. IMChannelHandler:连接生命周期
IMChannelHandler 继承SimpleChannelInboundHandler<IMSendInfo> ,泛型就是 Decoder 的产物类型。它只做三件事:
channelRead0:拿到IMSendInfo后按cmd通过 ProcessorFactory 从 Spring 容器取对应 Processor 执行userEventTriggered:收到IdleStateEvent(读空闲超时)说明客户端心跳断了,主动ctx.channel().close()handlerRemoved:连接移除时清理UserChannelCtxMap、删除 Redis 在线标记,并向业务层投递用户下线事件(还校验了 channel id,避免异地登录时误删新连接)
4. Processor 家族:命令分发
| Processor | 命令 | 职责 |
|---|---|---|
| LoginProcessor | LOGIN | JWT 校验、绑定 Channel、单端登录踢出、写 Redis 在线状态、发上线事件 |
| HeartbeatProcessor | HEART_BEAT | 回应心跳,每 10 次心跳给在线状态续期 |
| PrivateMessageProcessor | PRIVATE_MESSAGE | 查接收方 Channel 推送,回执写回结果队列 |
| GroupMessageProcessor | GROUP_MESSAGE | 群消息批量推送 |
| SystemMessageProcessor | SYSTEM_MESSAGE | 系统通知推送 |
| ForceLogoutProcessor | FORCE_LOGOUT | 服务端强制下线(封禁/注销) |
5. 连接管理与消息推送
两个关键机制:
Channel 路由表 : UserChannelCtxMap 用ConcurrentHashMap<userId, Map<terminal, ChannelHandlerContext>> 维护"用户→连接"的内存映射,支持同一用户多端(terminal)同时在线。登录成功后还会用 Netty 的 AttributeKey 把userId 、terminal 、devId 直接绑定在 Channel 上( LoginProcessor.java#L89-L102 ),后续任何 Handler 都能从 Channel 上取回用户身份——这是 Netty 的惯用技巧。
拉取式推送 :Netty 网关本身不处理业务,消息全链路看下图。 
对应到代码:
上行 :客户端消息经
IMChannelHandler解出后进入PrivateMessageProcessor,直接投递到 Redis 的IM_MESSAGE_PRIVATE_QUEUE队列,等业务层消费业务处理 :im-business 落库(
im_private_message表)、敏感词校验、查出接收方所在的serverId,把消息投到 按服务器实例分隔的队列 (队列key:serverId)下行 :每台 im-server 的 PullPrivateMessageTask (
@RedisMQListener(batchSize=100, period=10)定时批量拉取本机队列)→PrivateMessageProcessor.process()→ 从UserChannelCtxMap找到接收方 Channel 执行writeAndFlush推送;并通过ChannelFuture异步监听 推送成败,把回执写回结果队列供业务层更新消息状态。这种"拉取式 + serverId 路由"的设计,让 Netty 层可以 水平扩容 而互不干扰 另外两个值得注意的细节:异地登录踢出 : LoginProcessor.java#L59-L88 发现同 userId+terminal 已在线时,同机直接
writeAndFlush(踢出消息).addListener(CLOSE);跨机则把踢出指令投到目标服务器的 Redis 队列,由那台机器的PullForceLogoutTask执行——Netty 的ChannelFutureListener.CLOSE实现了"先送达再断开"心跳续期 :客户端定时发心跳包,
HeartbeatProcessor回应并每 10 次给 Redis 在线 key 续期,配合IdleStateHandler双保险判定在线状态
三、总结:Netty 概念与项目类的映射
| Netty 概念 | 项目中的落地 |
|---|---|
| ServerBootstrap / 主从 EventLoopGroup | TcpSocketServer、WebSocketServer 的 start() |
| ChannelPipeline / ChannelInitializer | 两个 initChannel() 中 addLast 组装的 Handler 链 |
| ByteBuf + 编解码器 | tcp/ws 两套 MessageProtocolEncoder/Decoder(长度+JSON / 文本帧) |
| SimpleChannelInboundHandler | IMChannelHandler(读、空闲、断开三类事件) |
| IdleStateHandler + IdleStateEvent | 读空闲超时主动断连 |
| AttributeKey(Channel 附件) | Channel 上绑定 userId/terminal/devId |
| writeAndFlush + ChannelFuture | 消息推送与异步回执监听 |
一句话概括: Netty 在这里扮演"纯长连接网关" ——负责连接保活、协议编解码、在线路由和消息进出,所有业务逻辑都隔离在 im-business,两者通过 Redis 队列解耦,这正是典型的 IM 接入层架构。