netty笔记(1)--ctx.write()和channel().write()的区别

本文是作者学习Netty过程中关于`ctx.write()`和`channel().write()`区别的笔记。Netty的ChannelPipeline包含Handler组成的双向链表,`ctx.write()`按照Handler顺序从前向后寻找Outbound Handler执行,而`channel().write()`则直接从尾部开始找第一个Outbound Handler。调整Handler顺序会影响两者的行为。在Outbound Handler中使用`channel().write()`可能引发死循环的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

刚学netty,一些细节上还不是很清楚,发现踩了很多坑。一点一点记录下来。
先看一个小demo

bootstrap.group(bossGroup,workerGroup)
                     .channel(NioServerSocketChannel.class)
                     .localAddress(new InetSocketAddress(port))
                     .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                        //重点看这里,先添加EchoServerOutHandler
                        //再添加EchoServerInHandler
                            ch.pipeline().addLast(outHandler);
                            ch.pipeline().addLast(inHandler);
                        }
                    });
//这里是EchoServerOutHandler 的详细代码
public class EchoServerOutHandler extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("來到了Out Handler");
        super.write(ctx, msg, promise);
        super.flush(ctx);
    }
}
//这里是EchoServerInHandler的详细代码
public class EchoServerInHandler extends SimpleChannelInboundHandler<ByteBuf> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        ByteBuf in = (ByteBuf) msg;
        System.out.println("server received:" + in.toString(CharsetUtil.UTF_8));
        ctx.write(Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump("aa")));
        //ctx.channel().write(Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump("aa")));
    }
}

1、先简单介绍下ChannelPipeline,ChannelPipeline将Handler封装为ChannelHandlerContext再按添加的顺序组合为一条双向链表,通过一个标志位来区别是Inbound还是outbound。
上面例子的handler顺序为
这里写图片描述
2、下面通过跟踪源码查看ctx.write()执行过程

第一步
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        ByteBuf in = (ByteBuf) msg;
        System.out.println("server received:" + in.toString(CharsetUtil.UTF_8));
        //执行ctx的write方法
        ctx.write(Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump("aa")));
        //ctx.channel().write(Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump("aa")));
    }
第二步
 private void write(Object msg, boolean flush, ChannelPromise promise) {
     //找到第一个处理该write方法的handler,具体细节看第三步
        AbstractChannelHandlerContext next = findContextOutbound();
        final Object m = pipeline.touch(msg, next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            if (flush) {
                next.invokeWriteAndFlush(m, promise);
            } else {
            //调用获取到的outbound的handler的write方法
                next.invokeWrite(m, promise);
            }
        } else {
            AbstractWriteTask task;
            if (flush) {
                task = WriteAndFlushTask.newInstance(next, m, promise);
            }  else {
                task = WriteTask.newInstance(next, m, promise);
            }
            safeExecute(executor, task, promise, m);
        }
    }
   第三步
   //从当前handler开始往前移动指针,找到第一个outbound的handler。这里当前的handler就是EchoServerOutHandler 了
      private AbstractChannelHandlerContext findContextOutbound() {
        AbstractChannelHandlerContext ctx = this;
        do {
            ctx = ctx.prev;
        } while (!ctx.outbound);
        return ctx;
    }
    第四步
    //最后就会执行到EchoServerOutHandler 的write方法了。查看控制台输出了“來到了Out Handler”
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("來到了Out Handler");
        super.write(ctx, msg, promise);
        super.flush(ctx);
    }

总结:所以如果我们是调用ctx.write()方法的话,会从当前的hander往前找第一个outbound来执行。记住一定要将OutBoundHandler先添加进ChannelPipeline。否则会跟期望的结果不一致。

我们可以试下将添加顺序换下,控制台不会打印“來到了Out Handler”。
3、接着继续跟踪源码查看channel().write()的执行过程

第一步
//执行ctx.channel().write()方法
@Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        ByteBuf in = (ByteBuf) msg;
        System.out.println("server received:" + in.toString(CharsetUtil.UTF_8));
        //ctx.write(Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump("aa")));
        ctx.channel().write(Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump("aa")));
    }
第二步
//将write方法委托给pipeline执行
 @Override
    public ChannelFuture write(Object msg) {
        return pipeline.write(msg);
    }   
    第三步
    //这里就是区别的地方了,pipeline直接选择最后一个handler来执行write。
    //所以这里先从tail开始找第一个是OutBound的handler。
      @Override
    public final ChannelFuture write(Object msg) {
        return tail.write(msg);
    }

总结:如果我们是使用channel().write()方法。可以不用考虑outbound和inbound的添加顺序。每次都会从tail往前找第一个是outbound的handler来执行。

我们可以试下无论如何修改添加顺序,控制台都会打印“來到了Out Handler”。

这里考虑一个问题,如果我们在outboundhandler调用channel().write()方法会出现什么问题?
死循环吗?试下就会知道了。

21:49:46:683 INFO 10944 --- [erverWorker-5-2] .r.p.t.h.AbstractServerTransportListener : [DUBBO] An error occurred while processing the http request with DefaultHttp11ServerTransportListener, Http1Request{method='POST', path='/carts', contentType='application/json;charset=UTF-8'}, dubbo version: 3.3.0, current host: 10.44.85.106 org.apache.dubbo.remoting.http12.exception.HttpStatusException: Invoker not found at org.apache.dubbo.rpc.protocol.tri.h12.AbstractServerTransportListener.doRoute(AbstractServerTransportListener.java:127) ~[dubbo-3.3.0.jar:3.3.0] at org.apache.dubbo.rpc.protocol.tri.h12.AbstractServerTransportListener.onBeforeMetadata(AbstractServerTransportListener.java:121) ~[dubbo-3.3.0.jar:3.3.0] at org.apache.dubbo.rpc.protocol.tri.h12.AbstractServerTransportListener.onMetadata(AbstractServerTransportListener.java:88) ~[dubbo-3.3.0.jar:3.3.0] at org.apache.dubbo.rpc.protocol.tri.h12.AbstractServerTransportListener.onMetadata(AbstractServerTransportListener.java:53) ~[dubbo-3.3.0.jar:3.3.0] at org.apache.dubbo.remoting.http12.netty4.h1.NettyHttp1ConnectionHandler.channelRead0(NettyHttp1ConnectionHandler.java:56) ~[dubbo-3.3.0.jar:3.3.0] at org.apache.dubbo.remoting.http12.netty4.h1.NettyHttp1ConnectionHandler.channelRead0(NettyHttp1ConnectionHandler.java:29) ~[dubbo-3.3.0.jar:3.3.0] at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:93) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at org.apache.dubbo.remoting.http12.netty4.h1.NettyHttp1Codec.channelRead(NettyHttp1Codec.java:49) ~[dubbo-3.3.0.jar:3.3.0] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103) ~[netty-codec-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103) ~[netty-codec-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346) ~[netty-codec-4.1.112.Final.jar:4.1.112.Final] at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318) ~[netty-codec-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.handler.codec.ByteToMessageDecoder.handlerRemoved(ByteToMessageDecoder.java:266) ~[netty-codec-4.1.112.Final.jar:4.1.112.Final] at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:537) ~[netty-codec-4.1.112.Final.jar:4.1.112.Final] at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469) ~[netty-codec-4.1.112.Final.jar:4.1.112.Final] at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) ~[netty-codec-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.112.Final.jar:4.1.112.Final] at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994) ~[netty-common-4.1.112.Final.jar:4.1.112.Final] at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.112.Final.jar:4.1.112.Final] at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.112.Final.jar:4.1.112.Final] at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na]
最新发布
07-19
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值