netty从入门到放弃--handler的生命周期

    科技2026-06-14  2

    根据我们对netty服务端学习的顺序,我们学习了服务端启动过程,学习了消息载体,pipeline以及handler的使用,接下来学习下handler内部执行的一些情况

    我们来定义一个 LifecycleChannelInboundHandler ,它继承了ChannelInboundHandlerAdapter,我们来实现父类的方法进行日志打印,观察它每个方法被调用的顺序以及时机

    客户端连接并且向服务端发送消息

    客户端主要逻辑

    @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void channelActive(ChannelHandlerContext ctx) { ctx.channel().writeAndFlush("hello, server"); } }); ch.pipeline().addLast(new StringEncoder()); }

    服务端主要逻辑

    @Slf4j public class LifecycleChannelInboundHandler extends SimpleChannelInboundHandler<String> { @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { log.info("channelRegistered() -- 客户端绑定线程: ip=[{}]", ctx.channel().remoteAddress()); super.channelRegistered(ctx); } @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { log.info("channelUnregistered() -- 客户端解除线程绑定: ip=[{}]", ctx.channel().remoteAddress()); super.channelUnregistered(ctx); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { log.info("channelActive() -- 客户端准备就绪, ip=[{}]", ctx.channel().remoteAddress()); super.channelActive(ctx); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { log.info("channelInactive() -- 客户端被关闭, ip=[{}]", ctx.channel().remoteAddress()); super.channelInactive(ctx); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { log.info("channelReadComplete() -- 本次读取客户端数据完毕: ip=[{}]", ctx.channel().remoteAddress()); super.channelReadComplete(ctx); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { log.info("userEventTriggered() -- 在规定时间内未进行读/写,链接断开"); ctx.channel().close(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { log.error("exceptionCaught() -- 异常处理", cause); super.exceptionCaught(ctx, cause); } @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { log.info("channelRead() -- 有数据处理, 内容: [{}]", msg); } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { log.info("handlerAdded() -- 增加逻辑处理器"); super.handlerAdded(ctx); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { log.info("handlerRemoved() -- 逻辑处理器被移除"); super.handlerRemoved(ctx); } }

    服务端执行结果

    INFO mxdshop.xyz.netty.server.handler.LifecycleChannelInboundHandler 62 - handlerAdded() -- 增加逻辑处理器 INFO mxdshop.xyz.netty.server.handler.LifecycleChannelInboundHandler 15 - channelRegistered() -- 客户端绑定线程: ip=[/127.0.0.1:52796] INFO mxdshop.xyz.netty.server.handler.LifecycleChannelInboundHandler 27 - channelActive() -- 客户端准备就绪, ip=[/127.0.0.1:52796] INFO mxdshop.xyz.netty.server.handler.LifecycleChannelInboundHandler 57 - channelRead() -- 有数据处理, 内容: [hello, server] INFO mxdshop.xyz.netty.server.handler.LifecycleChannelInboundHandler 39 - channelReadComplete() -- 本次读取客户端数据完毕: ip=[/127.0.0.1:52796]

    可以看到handler中重写了父类的部分方法,每个方法都有对当前方法被调用时的日志打印,接着我们运行服务器启动类,然后运行客户端启动类并且向服务端发送一个数据包

    handlerAdded(): 当检测到有新链接链接进来之后,调用ch.pipeline.addLast(xxxHandler)之后的回调函数,表示当前的这个handler已经成功添加到pipeline中了;

    channelRegistered(): 表示当前handler的逻辑处理和netty中的channel建立了连接,注册到了NioEventLoop,类似传统的socket编程中的accept方法;

    channelActive(): channel的所以的业务逻辑准备完毕,也即对应channel在pipeline中添加完毕所有的handler后调用该回调方法,并且也已经分配到了NIO线程后,这个方法被调用;

    channelRead(): 当客户端发来消息,会调用该方法,表示有消息可读;

    channelReadComplete(): 读取完毕一次完整的客户端消息之后服务端每次读完一次完整的数据之后,回调该方法,表示数据读取完毕。

    客户端从服务端断开

    当客户端下线,即从服务端断开链接时,对于服务端来讲就是一个channel被关闭了,这次观察下handler中每个方法的调用情况

    INFO mxdshop.xyz.netty.server.handler.LifecycleChannelInboundHandler 39 - channelReadComplete() -- 本次读取客户端数据完毕: ip=[/127.0.0.1:52525] INFO mxdshop.xyz.netty.server.handler.LifecycleChannelInboundHandler 33 - channelInactive() -- 客户端被关闭, ip=[/127.0.0.1:52525] INFO mxdshop.xyz.netty.server.handler.LifecycleChannelInboundHandler 21 - channelUnregistered() -- 客户端解除线程绑定: ip=[/127.0.0.1:52525] INFO mxdshop.xyz.netty.server.handler.LifecycleChannelInboundHandler 68 - handlerRemoved() -- 逻辑处理器被移除

    可以看到在客户端主动断开时,handler中方法的执行顺序如下:

    网上很多文章都写的是,客户端断开执行的方法的顺序为

    channelInactive() --> channelUnregistered() --> handlerRemoved()

    但是我这里调用时发现首先会调用一次 channelReadComplete(),通过上述的channelReadComplete()的讲解,它应该是在一条完整的消息读取完毕,也就是调用完毕channelRead()后才被回调的,那么这里的回调是怎么回事呢(如果有看过源码的大神,帮忙解答一下)? 猜测是在客户端断开时,发送了一次tcp的4次挥手,netty把这个动作当做了一次完整消息,所以回调了该方法,接下来了解下其他三个方法的作用;

    channelInactive(): 与channelActive()相对,这里是当链接断开,底层的tcp链接已经断开的情况下被调用;

    channelUnregistered(): 当链接被断开,netty中绑定的线程已经不再需要该链接了,就从线程池中对它解除绑定了,就会回调该方法;

    handlerRemoved(): 与handlerAdded()对应,当该channel把对应的链接解除绑定了,自然也不需要其中的handler了,当该handler从channel中被卸载,会回调该方法。

    最后,可以看到netty中一个handler的声明周期,用一张图表示

    Processed: 0.013, SQL: 9