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

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

bootstrap.group(bossGroup,workerGroup)
                     .channel(NioServerSocketChannel.class)
                     .localAddress(new InetSocketAddress(port))
                     .childHandler(new ChannelInitializer() {
                        @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顺序为
netty笔记(1)--ctx.write()和channel().write()的区别_第1张图片
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()方法会出现什么问题?
死循环吗?试下就会知道了。

你可能感兴趣的:(netty)