Netty 的 Channel 有哪些生命周期状态?ChannelHandler 的生命周期回调怎么用?
简化版
**Netty 的 Channel(连接)有一套生命周期,从「注册到 EventLoop」到「激活(可读写)」到「失活」到「注销」,每个阶段都有对应的回调事件,让你在合适的时机做处理(如连接建立时初始化、断开时清理)。**核心生命周期事件(ChannelInboundHandler 的回调):① channelRegistered——Channel 注册到 EventLoop(绑定了处理它的线程);② channelActive——Channel 激活,连接建立成功、可以读写了(服务端是有客户端连上、客户端是连上服务器,常在这里做「连接建立后的初始化」如发送握手消息、记录在线);③ channelRead——收到数据(每次读到数据触发);④ channelReadComplete——一批数据读完(一次读事件的所有数据读完,常在这里 flush);⑤ channelInactive——Channel 失活,连接断开了(常在这里做「断开后的清理」如移除在线记录、释放资源);⑥ channelUnregistered——从 EventLoop 注销。另外 ChannelHandler 自身有 handlerAdded/handlerRemoved(被加入/移出 pipeline 时)。核心记忆:channelActive(连接建立、做初始化)和 channelInactive(连接断开、做清理)是最常用的;channelRead(收数据)+ channelReadComplete(读完 flush)处理数据。
详细版
Channel 生命周期回调(ChannelInboundHandler):
| 回调 | 时机 | 常用于 |
|---|---|---|
| handlerAdded | Handler 加入 pipeline | 初始化 Handler 资源 |
| channelRegistered | Channel 注册到 EventLoop | 少用 |
| channelActive | 连接建立、可读写 | 连接建立初始化(握手、记录在线) |
| channelRead | 收到数据 | 处理接收的数据 |
| channelReadComplete | 一批数据读完 | flush、批量处理完成 |
| channelInactive | 连接断开 | 断开清理(移除在线、释放资源) |
| channelUnregistered | 从 EventLoop 注销 | 少用 |
| handlerRemoved | Handler 移出 pipeline | 释放 Handler 资源 |
| exceptionCaught | 发生异常 | 异常处理(记录、关闭连接) |
public class MyHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
// 连接建立,做初始化(如记录在线用户)
System.out.println("连接建立: " + ctx.channel().remoteAddress());
onlineUsers.add(ctx.channel());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 收到数据,处理
System.out.println("收到: " + msg);
ctx.fireChannelRead(msg); // 传给下一个 Handler(或不传,自己处理完)
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush(); // 一批数据读完,flush(批量发送响应)
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
// 连接断开,清理(如移除在线用户)
onlineUsers.remove(ctx.channel());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close(); // 异常时关闭连接
}
}
⚠️ 最常用、也最容易理解错时机的是
channelActive(连接建立)和channelInactive(连接断开)——它们是「连接建立后」和「连接断开后」的成对回调。channelActive在「连接真正可用(可以读写)」时触发——服务端是「一个客户端连上来了」、客户端是「连接上服务器了」;这是做「连接建立后的一次性初始化」的地方:发送握手/认证消息、把这个连接加入在线列表、启动心跳等。channelInactive在「连接断开」时触发(对端关闭、网络断开、主动关闭);这是做「连接断开后的清理」的地方:从在线列表移除、释放和这个连接相关的资源、通知其他系统用户下线等。成对使用(active 建立时加、inactive 断开时清理)能避免资源泄漏。另外exceptionCaught捕获处理过程中的异常——通常在这里记录日志并ctx.close()关闭出问题的连接(别让一个连接的异常影响其他)。
完整版教学
一、Channel 生命周期概览
先理解 Channel 的生命周期流转:
Channel 的生命周期(从创建到销毁):
1. 创建(new)——Channel 对象创建
2. 注册(registered)——注册到一个 EventLoop(绑定处理它的线程)
3. 激活(active)——连接建立、可以读写
4. 读数据(read / readComplete)——收发数据(active 期间反复)
5. 失活(inactive)——连接断开
6. 注销(unregistered)——从 EventLoop 注销
7. 销毁
对应的回调(ChannelInboundHandler):
channelRegistered → channelActive → channelRead(多次) →
channelReadComplete → ... → channelInactive → channelUnregistered
状态查询:
channel.isOpen():是否打开
channel.isRegistered():是否注册到 EventLoop
channel.isActive():是否激活(连接建立、可读写)
关键状态:
active:连接可用(能读写)——最重要的"可用"状态
inactive:连接断开——不可用
所以 Channel 生命周期 = 注册 → 激活 → 读写 → 失活 → 注销
每个阶段有回调,让你在合适时机处理
Channel 生命周期流转:创建 → 注册(registered,绑定 EventLoop)→ 激活(active,连接建立可读写)→ 读数据(read/readComplete,active 期间反复)→ 失活(inactive,连接断开)→ 注销(unregistered)→ 销毁。对应回调按顺序触发。状态查询:isOpen/isRegistered/isActive。关键状态:active(连接可用能读写)、inactive(连接断开)。理解「Channel 生命周期:注册→激活(active 可读写)→读写→失活(inactive 断开)→注销;对应回调按顺序;关键状态 active 可用/inactive 断开」,就理解了生命周期概览。
二、channelActive:连接建立
channelActive 是「连接建立、可读写」——最常用之一:
channelActive:Channel 激活,连接建立成功、可以读写了
触发时机:
服务端:一个客户端连上来了(accept 后,这个连接的 Handler 触发)
客户端:连接上服务器了
常用于(连接建立后的一次性初始化):
① 发送握手/认证消息(连上就发一个握手包)
② 把这个连接加入"在线列表"(记录谁在线)
③ 启动心跳(连接建立后开始心跳)
④ 记录连接建立日志
例:
public void channelActive(ChannelHandlerContext ctx) {
onlineChannels.add(ctx.channel()); // 加入在线
ctx.writeAndFlush(handshakeMessage); // 发握手
}
和 isActive 的关系:
channelActive 触发 = channel.isActive() 变 true
→ "连接可用了"的信号
所以 channelActive = 连接建立后的初始化点
channelActive 是「连接建立、可读写」——服务端是客户端连上、客户端是连上服务器。常用于连接建立后的一次性初始化:发送握手/认证消息、加入在线列表、启动心跳、记录日志。例:onlineChannels.add(channel) + writeAndFlush(握手)。channelActive 触发 = isActive() 变 true(连接可用信号)。理解「channelActive 连接建立可读写(服务端客户端连上/客户端连上服务器)、常用于连接建立初始化(发握手/加入在线/启动心跳/记日志)、触发=isActive 变 true」,就掌握了 channelActive。
三、channelInactive:连接断开
channelInactive 是「连接断开」——和 channelActive 成对:
channelInactive:Channel 失活,连接断开了
触发时机:
对端关闭连接、网络断开、本端主动关闭
→ 连接不再可用
常用于(连接断开后的清理):
① 从"在线列表"移除(这个连接下线了)
② 释放和这个连接相关的资源(缓存、状态)
③ 通知其他系统"用户下线"
④ 记录断开日志
例:
public void channelInactive(ChannelHandlerContext ctx) {
onlineChannels.remove(ctx.channel()); // 移除在线
cleanup(ctx.channel()); // 清理资源
}
和 channelActive 成对使用:
channelActive:连接建立 → 加入在线、分配资源
channelInactive:连接断开 → 移除在线、释放资源
→ 成对,避免资源泄漏(有加就有清)
区分 channelInactive 和 exceptionCaught:
channelInactive:连接正常/异常断开(都会触发)
exceptionCaught:处理过程出异常(不一定断开)
→ 一般 exceptionCaught 里 ctx.close() → 触发 channelInactive
所以 channelInactive = 连接断开后的清理点(和 active 成对)
channelInactive 是「连接断开」——对端关闭/网络断开/本端主动关闭。常用于连接断开后的清理:从在线列表移除、释放资源、通知用户下线、记日志。例:onlineChannels.remove(channel) + cleanup。和 channelActive 成对使用(active 建立时加入分配、inactive 断开时移除释放,避免资源泄漏)。区分 channelInactive(连接断开都触发)和 exceptionCaught(处理异常,一般里面 ctx.close() 触发 inactive)。理解「channelInactive 连接断开(对端关闭/网络断开/主动关闭)、常用于清理(移除在线/释放资源/通知下线)、和 channelActive 成对(有加就有清避免泄漏)」,就掌握了 channelInactive。
四、channelRead 与 channelReadComplete
channelRead 和 channelReadComplete 处理数据:
channelRead:收到数据
每次读到数据就触发(可能一次读事件触发多次 channelRead)
msg 是读到的数据(经过前面的解码器,可能是对象)
public void channelRead(ctx, Object msg) {
// 处理这条消息
process(msg);
// 传给下一个 Handler:ctx.fireChannelRead(msg)
// 或不传(自己处理完了)
// 注意:如果 msg 是 ByteBuf,用完要 release(引用计数)
}
channelReadComplete:一批数据读完
一次读事件的所有数据都读完了(channelRead 触发若干次后)
触发一次 channelReadComplete
常用于:
① flush(批量发送响应):
多次 channelRead 里 write(不 flush),最后 readComplete 里 flush
→ 批量发送,减少系统调用(提高吞吐)
public void channelReadComplete(ctx) {
ctx.flush();
}
channelRead vs channelReadComplete:
channelRead:每条数据(多次)
channelReadComplete:一批读完(一次)
→ read 处理数据、readComplete 收尾(flush)
引用计数(重要):
如果 channelRead 的 msg 是 ByteBuf(没经过解码器)
用完要 ReferenceCountUtil.release(msg)(否则内存泄漏)
→ 或用 SimpleChannelInboundHandler(自动 release)
所以 channelRead 处理数据、channelReadComplete flush 收尾
channelRead(收到数据,每次读到触发、可能多次、msg 是数据经过解码可能是对象)+ channelReadComplete(一批数据读完、触发一次)。channelRead 里 process(msg)、可 fireChannelRead 传给下一个 Handler。channelReadComplete 常用于 flush(多次 read 里 write 不 flush、最后 readComplete 里 flush 批量发送减少系统调用提高吞吐)。引用计数(重要):channelRead 的 msg 是 ByteBuf 时用完要 release(否则内存泄漏),或用 SimpleChannelInboundHandler(自动 release)。理解「channelRead 收数据(每次读到触发多次)、channelReadComplete 一批读完(触发一次常用于 flush 批量发送);ByteBuf 用完要 release(否则泄漏)或用 SimpleChannelInboundHandler 自动 release」,就掌握了数据处理回调。
五、Handler 自身的生命周期
ChannelHandler 自身也有生命周期回调:
handlerAdded:Handler 被加入 pipeline 时
→ 初始化 Handler 自己的资源(如分配缓冲、注册定时任务)
handlerRemoved:Handler 被移出 pipeline 时
→ 释放 Handler 自己的资源
区分 Handler 生命周期 vs Channel 生命周期:
handlerAdded/Removed:Handler 加入/移出 pipeline(Handler 的生命周期)
channelActive/Inactive:连接建立/断开(Channel 的生命周期)
handlerAdded 早于 channelActive:
Handler 先被加入 pipeline(handlerAdded)
连接激活时才 channelActive
→ 初始化 Handler 资源用 handlerAdded、连接建立初始化用 channelActive
@Sharable:
一个 Handler 实例被多个 Channel 共享(无状态的 Handler 可共享)
→ 加 @Sharable 注解
★ 有状态的 Handler 不能共享(每个连接一个新实例)
否则多个连接共用状态会错乱
所以 Handler 有自己的生命周期(Added/Removed),和 Channel 的分开
ChannelHandler 自身生命周期:handlerAdded(被加入 pipeline 时,初始化 Handler 资源)、handlerRemoved(被移出时,释放 Handler 资源)。区分 Handler 生命周期(Added/Removed,Handler 加入移出 pipeline)vs Channel 生命周期(Active/Inactive,连接建立断开):handlerAdded 早于 channelActive。@Sharable:无状态 Handler 可被多个 Channel 共享(加注解)、有状态 Handler 不能共享(每个连接一个新实例,否则状态错乱)。理解「Handler 自身生命周期:handlerAdded(加入 pipeline 初始化资源)/handlerRemoved(移出释放);区分 Handler 生命周期 vs Channel 生命周期(handlerAdded 早于 channelActive);@Sharable 无状态可共享、有状态不能共享(否则错乱)」,就掌握了 Handler 自身生命周期。
六、实践与总结
总结生命周期回调的实践:
常用回调的实践:
channelActive(连接建立):
→ 加入在线、发握手、启动心跳、记日志
channelInactive(连接断开):
→ 移除在线、释放资源、通知下线(和 active 成对)
channelRead(收数据):
→ 处理消息(ByteBuf 用完 release)
channelReadComplete(读完):
→ flush(批量发送)
exceptionCaught(异常):
→ 记录日志、ctx.close()(关闭出问题的连接)
handlerAdded/Removed(Handler 加入移出):
→ 初始化/释放 Handler 资源
注意点:
① channelActive 和 channelInactive 成对(加了资源要清)
② ByteBuf 引用计数(channelRead 的 ByteBuf 用完 release)
→ 或用 SimpleChannelInboundHandler(自动 release)
③ 有状态 Handler 别加 @Sharable(每连接一个实例)
④ 回调里别做阻塞操作(会阻塞 EventLoop,影响该线程管的所有连接)
⑤ exceptionCaught 处理异常、关闭出问题的连接
核心总结:
Channel 生命周期:注册→激活(active)→读写→失活(inactive)→注销
channelActive 连接建立初始化、channelInactive 断开清理(成对)
channelRead 收数据 + channelReadComplete flush
exceptionCaught 异常处理、Handler 有 handlerAdded/Removed
生命周期回调实践:channelActive(加入在线/发握手/启动心跳)、channelInactive(移除在线/释放资源,和 active 成对)、channelRead(处理消息,ByteBuf release)、channelReadComplete(flush)、exceptionCaught(记日志+ctx.close())、handlerAdded/Removed(初始化/释放 Handler 资源)。注意:active/inactive 成对、ByteBuf 引用计数、有状态 Handler 别 @Sharable、回调里别阻塞(阻塞 EventLoop 影响所有连接)。理解「实践:channelActive 建立初始化/channelInactive 断开清理(成对)/channelRead 处理数据 release/channelReadComplete flush/exceptionCaught 关闭连接;注意:成对/引用计数/有状态别 Sharable/别阻塞 EventLoop」,就掌握了生命周期回调的实践。
记忆钩子:「Netty Channel 生命周期:注册(channelRegistered 绑定 EventLoop)→激活(★channelActive 连接建立可读写)→读数据(channelRead 每次读到多次)→读完(channelReadComplete 一批读完常 flush)→失活(★channelInactive 连接断开)→注销(channelUnregistered);channelActive 常用于连接建立初始化(加入在线/发握手/启动心跳)、channelInactive 常用于断开清理(移除在线/释放资源)、两者成对(有加就有清避免泄漏);channelRead 的 ByteBuf 用完要 release(否则泄漏)或用 SimpleChannelInboundHandler 自动 release;exceptionCaught 异常处理(记日志+ctx.close());Handler 自身有 handlerAdded/Removed(加入移出 pipeline),@Sharable 无状态可共享有状态不能;回调里别阻塞(阻塞 EventLoop 影响所有连接)」。
七、常见误区与追问
- 误区:channelActive 和 channelRegistered 一样。 channelRegistered 是 Channel 注册到 EventLoop(绑定了处理线程,但连接不一定可用);channelActive 是连接真正建立、可以读写了;channelActive 在 channelRegistered 之后;做「连接建立后的初始化」用 channelActive(此时能读写)。
- 误区:channelRead 里的 ByteBuf 用完不用管。 如果 msg 是 ByteBuf(没经过解码器转成对象),用完必须 ReferenceCountUtil.release(msg) 释放(Netty 的 ByteBuf 是引用计数管理的,不 release 会内存泄漏);或者用 SimpleChannelInboundHandler(它在 channelRead0 结束后自动 release)。
- 误区:所有 Handler 都可以加 @Sharable 共享。 只有无状态的 Handler 才能加 @Sharable 被多个 Channel 共享;有状态的 Handler(内部有随连接变化的字段)不能共享——如果共享,多个连接会共用同一份状态、互相污染、数据错乱;有状态 Handler 每个连接要一个新实例。
- 误区:可以在回调里做阻塞操作(如数据库查询)。 不能——ChannelHandler 的回调在 EventLoop 线程执行,一个 EventLoop 线程管理多个 Channel;如果在回调里做阻塞操作(数据库、慢 IO、加锁等待),会阻塞这个 EventLoop 线程、影响它管理的所有连接;耗时操作要提交到业务线程池执行。
- 追问:channelActive 和 channelInactive 分别在什么时机触发,常用来做什么? channelActive 在连接建立、可以读写时触发(服务端是客户端连上、客户端是连上服务器),常用于连接建立后的一次性初始化:发送握手/认证消息、把连接加入在线列表、启动心跳、记录日志;channelInactive 在连接断开时触发(对端关闭、网络断开、主动关闭),常用于连接断开后的清理:从在线列表移除、释放和该连接相关的资源、通知用户下线;两者成对使用(active 时加、inactive 时清),避免资源泄漏。
- 追问:channelRead 和 channelReadComplete 有什么区别? channelRead 在每次收到数据时触发(一次读事件可能触发多次 channelRead,每次一条数据/消息);channelReadComplete 在一批数据全部读完时触发一次(多次 channelRead 之后);常见用法是:多次 channelRead 里 write 响应(不 flush),最后在 channelReadComplete 里统一 flush,这样批量发送、减少系统调用、提高吞吐。
- 追问:ChannelHandler 的 handlerAdded 和 channelActive 有什么区别? handlerAdded 是 Handler 被加入 ChannelPipeline 时触发(Handler 自身的生命周期,此时连接不一定建立),用于初始化 Handler 自己的资源;channelActive 是连接建立、可读写时触发(Channel 的生命周期),用于连接建立后的初始化;handlerAdded 早于 channelActive;一个是「Handler 加入 pipeline」、一个是「连接激活」,是两个不同层次的生命周期。
八、加强记忆
Netty 的 Channel 有一套生命周期,每个阶段有回调:注册(channelRegistered,绑定 EventLoop)→ 激活(channelActive,连接建立可读写)→ 读数据(channelRead,每次读到触发多次)→ 读完(channelReadComplete,一批读完触发一次)→ 失活(channelInactive,连接断开)→ 注销(channelUnregistered)。最常用的两个成对回调:channelActive(连接建立) 用于连接建立后的一次性初始化(发送握手/认证、加入在线列表、启动心跳、记日志);channelInactive(连接断开) 用于断开后的清理(从在线列表移除、释放资源、通知用户下线)——成对使用避免资源泄漏(有加就有清)。channelRead(收数据) 处理消息(如果 msg 是 ByteBuf 用完要 release,否则内存泄漏;或用 SimpleChannelInboundHandler 自动 release);channelReadComplete(读完) 常用于 flush(批量发送)。exceptionCaught(异常) 记日志 + ctx.close() 关闭出问题的连接。ChannelHandler 自身有 handlerAdded/handlerRemoved(加入/移出 pipeline),@Sharable 无状态可共享、有状态不能。回调里别做阻塞操作(阻塞 EventLoop 影响它管的所有连接)。一句话「Channel 生命周期:注册→激活(channelActive 连接建立可读写)→读数据(channelRead 多次)→读完(channelReadComplete 常 flush)→失活(channelInactive 断开)→注销;channelActive 建立初始化+channelInactive 断开清理(成对避免泄漏);channelRead 的 ByteBuf 用完 release;exceptionCaught 关闭连接;Handler 有 handlerAdded/Removed,回调别阻塞 EventLoop」。