Skip to content

一、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 分离。先看全局结构。 2026-10-06_162336_602

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)这个中间消息模型。 2026-10-06_162433_765

编解码要点:

  • 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 的产物类型。它只做三件事:

  1. channelRead0 :拿到IMSendInfo 后按cmd 通过 ProcessorFactory 从 Spring 容器取对应 Processor 执行
  2. userEventTriggered :收到IdleStateEvent (读空闲超时)说明客户端心跳断了,主动ctx.channel().close()
  3. handlerRemoved :连接移除时清理UserChannelCtxMap 、删除 Redis 在线标记,并向业务层投递用户下线事件(还校验了 channel id,避免异地登录时误删新连接)

4. Processor 家族:命令分发 ​

Processor命令职责
LoginProcessorLOGINJWT 校验、绑定 Channel、单端登录踢出、写 Redis 在线状态、发上线事件
HeartbeatProcessorHEART_BEAT回应心跳,每 10 次心跳给在线状态续期
PrivateMessageProcessorPRIVATE_MESSAGE查接收方 Channel 推送,回执写回结果队列
GroupMessageProcessorGROUP_MESSAGE群消息批量推送
SystemMessageProcessorSYSTEM_MESSAGE系统通知推送
ForceLogoutProcessorFORCE_LOGOUT服务端强制下线(封禁/注销)

5. 连接管理与消息推送 ​

两个关键机制:

Channel 路由表 : UserChannelCtxMap 用ConcurrentHashMap<userId, Map<terminal, ChannelHandlerContext>> 维护"用户→连接"的内存映射,支持同一用户多端(terminal)同时在线。登录成功后还会用 Netty 的 AttributeKey 把userId 、terminal 、devId 直接绑定在 Channel 上( LoginProcessor.java#L89-L102 ),后续任何 Handler 都能从 Channel 上取回用户身份——这是 Netty 的惯用技巧。

拉取式推送 :Netty 网关本身不处理业务,消息全链路看下图。 2026-10-06_162617_043

对应到代码:

  • 上行 :客户端消息经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 / 主从 EventLoopGroupTcpSocketServer、WebSocketServer 的 start()
ChannelPipeline / ChannelInitializer两个 initChannel() 中 addLast 组装的 Handler 链
ByteBuf + 编解码器tcp/ws 两套 MessageProtocolEncoder/Decoder(长度+JSON / 文本帧)
SimpleChannelInboundHandlerIMChannelHandler(读、空闲、断开三类事件)
IdleStateHandler + IdleStateEvent读空闲超时主动断连
AttributeKey(Channel 附件)Channel 上绑定 userId/terminal/devId
writeAndFlush + ChannelFuture消息推送与异步回执监听

一句话概括: Netty 在这里扮演"纯长连接网关" ——负责连接保活、协议编解码、在线路由和消息进出,所有业务逻辑都隔离在 im-business,两者通过 Redis 队列解耦,这正是典型的 IM 接入层架构。

Released under the MIT License.