Loading

Netty ChannelPipeline组件

ChannelPipeline

注:以下Netty版本为4.1.30.Final;

ChannelPipeline概念

一条Netty Channel通道需要很多的Handler业务处理器来处理业务,每条Channel通道内部都是由一条流水线Pipeline将Handler装配起来;

Netty的业务处理器流水线ChannelPipeline是基于责任链模式设计的(类似于Servlet的Filter过滤器),内部是一个双向链表结构,能够支持动态地添加和删除Handler业务处理器,它负责处理和拦截inbound(入站)或者outbound(出站)的事件和操作;(也可以这样理解:ChannelPipeline是保存ChannelHandler的 List,拦截穿过Channel的I/O事件,ChannelPipeline实现了拦截器的一种高级形式,使得用户可以对事件的处理以及ChannelHanler之间交互获得完全的控制权);

 

每一个新创建的Channel(也可以说是每创建一个新的连接) 都将会被分配一个新的ChannelPipeline,这项关联是永久性的; Channel 既不能附加另外一个 ChannelPipeline,也不能分离其当前的;在 Netty 组件的生命周期中,这是一项固定的操作,不需要开发人员的任何干预;

事件将会被ChannelInboundHandler或者ChannelOutboundHandler处理;随后,通过调用ChannelHandlerContext实现,它将被转发给同一超类型的下一个ChannelHandler;

 

修改ChannelPipeline布局

通过ChannelHandlerContext触发的操作的事件流,ChannelHandler可以通知其所属的ChannelPipeline中的下一 个ChannelHandler,甚至可以动态修改它所属的ChannelPipeline的布局;

 

用于修改ChannelPipeline布局的方法

方法 描述

ChannelPipeline#addFirst

ChannelPipeline#addBefore

ChannelPipeline#addAfter

ChannelPipeline#addLast

将一个ChannelHandler添加到ChannelPipeline中;
ChannelPipeline#remove 将一个ChannelHandler从ChannelPipeline中移除;
ChannelPipeline#replace 将ChannelPipeline中的一个ChannelHandler替换为另一个ChannelHandler

 

ChannelPipeline热插拔Handler处理器演示

演示调用流水线实例的 remove(ChannelHandler)方法,从流水线动态地删除一个 Handler实例;

查看代码
public class PipelineHotOperateTester {
    private final static Logger logger = LoggerFactory.getLogger(PipelineHotOperateTester.class);

    static class SimpleInHandlerA extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            logger.info("SimpleInHandlerA channelRead");
            super.channelRead(ctx, msg);
            // 从流水线删除当前业务处理器
            ctx.pipeline().remove(this);
        }
    }

    static class SimpleInHandlerB extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            logger.info("SimpleInHandlerB channelRead");
            super.channelRead(ctx, msg);
        }
    }

    static class SimpleInHandlerC extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            logger.info("SimpleInHandlerC channelRead");
            super.channelRead(ctx, msg);
        }
    }

    @Test
    public void testPipelineHotOperate() {
        ChannelInitializer<EmbeddedChannel> initializer = new ChannelInitializer<EmbeddedChannel>() {
            @Override
            protected void initChannel(EmbeddedChannel ch) throws Exception {
                ch.pipeline().addLast(new SimpleInHandlerA());
                ch.pipeline().addLast(new SimpleInHandlerB());
                ch.pipeline().addLast(new SimpleInHandlerC());
            }
        };

        EmbeddedChannel channel = new EmbeddedChannel(initializer);
        ByteBuf buf = Unpooled.buffer();
        buf.writeInt(1);

        // 第一次向通道写入站报文(或数据包)
        channel.writeInbound(buf);
        // 第二次向通道写入站报文(或数据包)
        channel.writeInbound(buf);
        // 第三次向通道写入站报文(或数据包)
        channel.writeInbound(buf);
    }
}

执行结果如下:

从运行结果中可以看出,在SimpleInHandlerA从流水线中删除后,在后面的入站流水处理中(第二次和第三次入站处理流程),SimpleInHandlerA已经不再被调用了;

具体原因:ChannelInitializer作为一个入站处理器,只初始化一次通道,不会重复调用channelRegiested方法注册通道,在源码中,ChannelInitializer在它的注册完成channelRegistered回调方法中使用了ctx.pipeline().remove(this)将自己从流水线中删除,因此ChannelInitializer只被执行一次;

ChannelInitializer#channelRegistered

ChannelInitializer#initChannel(io.netty.channel.ChannelHandlerContext)

ChannelInitializer#remove

这里会删除通道初始化处理器;

ChannelInitializer在完成了通道的初始化之后将自己从流水线中删除的主要:是因为一条通道流水线只需要做一次装配的工作;

 

ChannelPipeline事件传播

下面以入站事件为例;虽然被调用的Channel或ChannelPipeline上的write方法将一直传播事件通过整个ChannelPipeline,但是在ChannelHandler的级别上,事件从一个ChannelHandler到下一个ChannelHandler的移动是由ChannelHandlerContext上的调用完成的;

 

ChannelPipeline要想调用从某个特定的ChannelHandler开始的处理过程,必须获取到在ChannelPipeline中该ChannelHandler之前的ChannelHandler所关联的ChannelHandlerContext;这个ChannelHandlerContext将调用和它所关联的ChannelHandler之后的ChannelHandler;

ChannelPipeline从特定的ChanneHandler开始处理流程,这可以减少将事件传递对它不需关注的ChannelHandler所带来的开销;

 

ChannelPipeline入站流程演示

新建三个入站处理器,在ChannelInitializer初始化处理器的initChannel方法中把它们加入到ChannelPipeline流水线,添加顺序为A->B->C;

测试如下:

查看代码
public class InPipelineTest {
    private final static Logger logger = LoggerFactory.getLogger(InPipelineTest.class);

    static class SimpleInHandlerA extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            logger.info("SimpleInHandlerA channelRead被调用");
            super.channelRead(ctx, msg);
        }
    }

    static class SimpleInHandlerB extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            logger.info("SimpleInHandlerB channelRead被调用");
            super.channelRead(ctx, msg);
        }
    }

    static class SimpleInHandlerC extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            logger.info("SimpleInHandlerC channelRead被调用");
            super.channelRead(ctx, msg);
        }
    }

    @Test
    public void testPipelineInBound() {
        ChannelInitializer<EmbeddedChannel> initializer = new ChannelInitializer<EmbeddedChannel>() {
            @Override
            protected void initChannel(EmbeddedChannel ch) throws Exception {
                ch.pipeline().addLast(new SimpleInHandlerA());
                ch.pipeline().addLast(new SimpleInHandlerB());
                ch.pipeline().addLast(new SimpleInHandlerC());
            }
        };

        EmbeddedChannel channel = new EmbeddedChannel(initializer);
        ByteBuf buffer = Unpooled.buffer();
        buffer.writeInt(1);
        // 向通道写入一个入站报文
        channel.writeInbound(buffer);
    }
}

执行结果如下:

入站顺序的流动顺序:A->B->C,添加在前面的Handler,执行也在前面;如下图;

注:Handler父类中的ChannelRead方法中会调用fireChannelRead方法,fireXXX是传播该XXX操作的结果到下一个Handler调用,当Handler不调用父类的ChannelRead方法,则channelRead事件不会传播到下一个Handler;

 

ChannelPipeline出站流程演示

新建三个出站处理器,在ChannelInitializer初始化处理器的initChannel方法中把它们加入到ChannelPipeline流水线,添加顺序为A->B->C;

查看代码
public class OutPipelineTest {
    private final static Logger logger = LoggerFactory.getLogger(OutPipelineTest.class);

    static class SimpleOutHandlerA extends ChannelOutboundHandlerAdapter {
        @Override
        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
            logger.info("SimpleOutHandlerA write");
            super.write(ctx, msg, promise);
        }
    }

    static class SimpleOutHandlerB extends ChannelOutboundHandlerAdapter {
        @Override
        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
            logger.info("SimpleOutHandlerB write");
            super.write(ctx, msg, promise);
        }
    }

    static class SimpleOutHandlerC extends ChannelOutboundHandlerAdapter {
        @Override
        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
            logger.info("SimpleOutHandlerC write");
            super.write(ctx, msg, promise);
        }
    }

    @Test
    public void testPipelineOutBound() {
        ChannelInitializer<EmbeddedChannel> initializer = new ChannelInitializer<EmbeddedChannel>() {
            @Override
            protected void initChannel(EmbeddedChannel ch) throws Exception {
                ch.pipeline().addLast(new SimpleOutHandlerA());
                ch.pipeline().addLast(new SimpleOutHandlerB());
                ch.pipeline().addLast(new SimpleOutHandlerC());
            }
        };

        EmbeddedChannel channel = new EmbeddedChannel(initializer);
        ByteBuf buffer = Unpooled.buffer();
        buffer.writeInt(1);
        // 向通道写一个出站报文
        channel.writeOneOutbound(buffer);
    }
}

执行结果如下:

在以上出站处理器的write方法中,打印当前Handler业务处理器的信息,然后调用父类的write方法,而这里父类的write方法,则会将出站数据通过通道流水线发送到下一个出站处理器;

ChannelPipeline中添加出站处理器的顺序为A->B->C,而出站流水处理依次为C->B->A,最后添加的出站处理器反而是最先执行的;如下图;

 

截断流水线的入站/出站处理传播过程演示

出站的过程中,如果由于业务条件不满足,需要截断流水线的处理,不让处理传播到下一站,这个时候应该如何出来?

 

  • 截断流水线的入站

在上面的ChannelPipeline入站演示的例程中,将SimpleInHandler2修改如下:

static class SimpleInHandlerB extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        logger.info("SimpleInHandlerB channelRead被调用");
    }
}

将原本SimpleHandlerB中调用父类的channelRead方法删掉;执行结果如下:

入站处理器C没有执行到,说明处理流水线被成功地截断,如下图:

以上例子中通过不调用父类的channelRead方法,截断流水线的执行;而在channelRead方法中,将入站处理结果传播到下一个Handler还有一种方法:ChannelHanlderContext的fireChannelRead方法;
如果要截断其他的入站处理的流水线操作(操作以XXX指代),可采取下面的方式处理:

  1. 不调用父类的channelXXX方法;
  2. 不调用ChannelHandlerContext的fireChannelXXX方法;

入站处理器一般会继承ChannelInboundHandlerAdapter适配器,而该适配器的默认的入站实现,主要是进行入站操作的流水线传播,并且是通过ChannelHanlderContext实例完成的;

ChannelInboundHandlerAdapter#channelRead

channelRead方法会通过ChannelHandlerContext上下文进行入站读操作的流水线传播;

 

  • 截断流水线的出站

在上面的ChannelPipeline出站演示的例程中,将SimpleOutHandlerB修改如下:

static class SimpleOutHandlerB extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        logger.info("SimpleOutHandlerB write");
    }
}

将SimpleOutHanlderB中调用父类的write方法删掉,执行结果如下:

出站处理器A没有执行到,说明处理流水线被成功地截断,如下图

 

ChannelHandlerContext概念

ChannelHandlerContext代表了ChannelHandler和ChannelPipeline之间的关联,每当有ChannelHandler添加到ChannelPipeline中时,都会创建ChannelHandlerContext;ChannelHandlerContext的主要功能是管理它所关联的ChannelHandler和在同一个ChannelPipeline中的其他ChannelHandler之间的交互,保存Channel相关的所有上下文信息,同时关联一个ChannelHandler对象即ChannelHandlerContext中包含一个具体的事件处理器 ChannelHandler,同时ChannelHandlerContext中也绑定了对应的pipeline和channel的信息,方便对ChannelHandler进行调用;

实际上,ChannelPipeline通道流水线在没有加入任何处理器之前,装配了两个默认的处理器上下文:一个头部上下文叫做HeadContext,一个尾部上下文叫做TailContext;ChannelPipeline的创建、初始化除了保存一些必要的属性外,核心就在于创建了HeadContext头节点和TailContext尾节点;

 

DefaultChannelPipeline#DefaultChannelPipeline

DefaultChannelPipeline是流水线默认实现类,而HeadContext和TailContext是DefaultChannelPipeline的一个内部类;

 

HeadContext

DefaultChannelPipeline.HeadContext

HeadContext既是出站处理器、也是入站处理器;

这里Unsafe类是Netty内部的类,专供Netty内部使用,应用程序不能使用,因此取名为unsafe;

 

入站( 从Channel到Handler)读操作

 

出站(从Handler到Channel)读取传输数据

 

出站(从Handler到Channel)写操作

 

TailContext

TailContext是流水线默认实现类DefaultChannelPipeline的一个内部类;

 

DefaultChannelPipeline.TailContext

流水线尾部的TailContext不仅仅是一个上下文类,而且是一个入站处理器类,实现了所有入站处理回调方法,这些回调实现的主要工作,基本上都是收尾处理的,如释放缓冲区对象、完成异常处理等;

 

ChannelPipeline入站和出站的双向链表操作

完整的出站和入站处理流转过程,都是通过调用流水线ChannelPipeline实例的相应的出/入站方法开启的;

下面分别截取了ChannelPipeline的一个入站和出站的操作,如下:

启动ChannelPipeline流水线的入站读的流程

DefaultChannelPipeline#fireChannelRead

启动流水线的入站读

以流水线的入站读的启动过程为例,ChannelPipeline的入站流程是从fireXXX方法开始的(XXX表具体入站操作,入站读的操作为ChannelRead),而ChannelPipeline通过AbstractChannelHandlerContext#invokeChannelRead方法从流水线的head节点开始,将入站的msg数据沿着流水线上的入站处理器从头往后传递;

 

ChannelPipeline流水线链表的节点其默认的实现类为AbstractChannelHandlerContext抽象类,此类也是HeadContext与TailContext 的父类;ChannelPipeline内部的双向链表的指针维护,以及节点前驱和后继的获取方法都在这个类中实现;

AbstractChannelHandlerContext的核心成员如下:

// 双向链表的后继指针
volatile AbstractChannelHandlerContext next;
// 双向链表的前驱指针
volatile AbstractChannelHandlerContext prev;
// 是否入站节点的标志
private final boolean inbound;
// 是否出站节点的标志
private final boolean outbound;
// 所属的流水线
private final DefaultChannelPipeline pipeline;
// 上下文节点的名称,在加入流水线时可用于指定
private final String name;
// 节点的执行线程,如果没有特别设置,则为通道的I/O线程
final EventExecutor executor;

 

AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)

static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    // 获取后继的处理线程
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        // 如果当前线程为后继的处理线程,则执行后继上下文所包装的处理器
        next.invokeChannelRead(m);
    } else {
        // 如果当前处理线程不是后继的处理线程,则提交到后继处理线程去排队
        // 保障该节点的处理器被设置的线程调用,避免发生线程安全问题
        executor.execute(new Runnable() {
            @Override
            public void run() {
                // 提交到后继处理线程
                next.invokeChannelRead(m);
            }
        });
    }
}

 

AbstractChannelHandlerContext#invokeChannelRead(java.lang.Object)

private void invokeChannelRead(Object msg) {
    // 判断该Context节点下的handler是否已添加到流水线
    if (invokeHandler()) {
        try {
            // 执行下一个Context节点所包装的处理器的事件
            ((ChannelInboundHandler) handler()).channelRead(this, msg);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    } else {
        // 如果该Context节点下的handler没有被添加到流水线,则向后找一个入站类型的节点
        fireChannelRead(msg);
    }
}

上面代码中用于判断的invokeHandler方法是用于判断Context节点下的handler是否已添加到流水线,而该方法里用于判断的handlerState变量是通过对引用类型的原子类变量HANDLER_STATE_UPDATER更新的;

AbstractChannelHandlerContext#HANDLER_STATE_UPDATER

 

由于head节点是内置且已添加的,上面的invokeChannelRead方法则会执行channelRead的方法;

DefaultChannelPipeline.HeadContext#channelRead

该方法同样也是会触发到下一个handler的调用,最终会调用到AbstractChannelHandlerContext#fireChannelRead;

 

AbstractChannelHandlerContext#fireChannelRead

 

该方法先通过findContextInbound方法向后获取一个入站类型的节点,之后的执行流程跟之前的head节点一样;

 

AbstractChannelHandlerContext#findContextInbound

private AbstractChannelHandlerContext findContextInbound() {
    AbstractChannelHandlerContext ctx = this;
    do {
        // 向后查找,一直到末尾或者找到入站类型节点为止
        ctx = ctx.next;
    } while (!ctx.inbound);
    return ctx;
}

 

执行流程如下:

 

启动ChannelPipeline流水线的出站写

DefaultChannelPipeline#write(java.lang.Object)

出站从后往前传递,传播方向跟入站是相反的,其余流程大致相同;

 

AbstractChannelHandlerContext#findContextOutbound

private AbstractChannelHandlerContext findContextOutbound() {
    AbstractChannelHandlerContext ctx = this;
    do {
        // 向前查找,直到头部或者找到一个出站 Context 为止
        ctx = ctx.prev;
    } while (!ctx.outbound);
    return ctx;
}

在ChannelPipeline双向链表中向前找到一个出站的节点;

 

Channel,Handler,ChannelHandlerContext三者的关系

Channel通道拥有一条ChannelPipeline通道流水线,并且ChannelPipeline中Channel属性与其绑定,每一个流水线节点为一个 ChannelHandlerContext上下文对象,每一个上下文中包裹了一个ChannelHandler通道处理器,但通道流水线在没有加入任何通道处理器之前,装配了两个默认的处理器上下文:一个是位于头部的HeadContext,另一个是位于尾部的TailContext;在ChannelHandler通道处理器的入站/出站处理方法中,Netty都会传递一个Context上下文实例作为实际参数;  

 

对于客户端和服务端,入站和出站是相对的;以客户端应用程序为例,如果事件的传播方向是从客户端到服务端的(发送请求到服务端),那么对于客户端为出站的(即客户端发送给服务端的数据会通过pipeline中的一系列ChannelOutboundHandler,并被这些Handler处理),对于服务端是入站的;

 

Channel、ChannelPipeline、ChannelHandlerContext 都可以调用此方法,前两者的事件传播会经过整个ChannelPipeline,而ChannelHandlerContext就只会在后续的Handler里面传播;

ChannelInboundHandler之间的传递,主要通过调用ChannelHandlerContext里面的fireXXX方法(流水线操作以XXX指代)来实现将XXX的结果传递到下个Handler的调用;

 

多个入站出站ChannelHandler的执行顺序分析

多个入站出站ChannelHandler的执行顺序测试

  • 服务端

服务端启动类添加Handler

查看代码
serverBootstrap.group(bossGroup, workGroup)
    .channel(NioServerSocketChannel.class)
    .option(ChannelOption.SO_BACKLOG, 1024)
    .childHandler(new ChannelInitializer() {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ch.pipeline().addLast(new InboundHandler1());
            ch.pipeline().addLast(new InboundHandler2());
            ch.pipeline().addLast(new OutboundHandler1());
            ch.pipeline().addLast(new OutboundHandler2());
        }
    });

 

OutboundHandler1

查看代码
public class OutboundHandler1 extends ChannelOutboundHandlerAdapter {
    private final static Logger log = LoggerFactory.getLogger(OutboundHandler1.class);

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ByteBuf data = (ByteBuf) msg;
        log.info("|OutboundHandler1 write : " + data.toString(CharsetUtil.UTF_8));
        ctx.write(Unpooled.copiedBuffer( "OutboundHandler1 " +data.toString(CharsetUtil.UTF_8), CharsetUtil.UTF_8));
        ctx.flush();
    }
}

  

OutboundHandler2

查看代码
public class OutboundHandler2 extends ChannelOutboundHandlerAdapter {
    private final static Logger log = LoggerFactory.getLogger(OutboundHandler2.class);

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ByteBuf data = (ByteBuf) msg;
        log.info("|OutboundHandler2 write : " +data.toString(CharsetUtil.UTF_8));
        ctx.write(Unpooled.copiedBuffer("OutboundHandler2 " + data.toString(CharsetUtil.UTF_8), CharsetUtil.UTF_8));
        ctx.flush();
    }
}

 

InboundHandler1

查看代码
public class InboundHandler1 extends ChannelInboundHandlerAdapter {
    private final static Logger log = LoggerFactory.getLogger(InboundHandler1.class);

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        ByteBuf data = (ByteBuf) msg;
        log.info("|InboundHandler1 channelRead 服务端收到数据:" + data.toString(CharsetUtil.UTF_8));

        // 执行下一个InboundHandler
        ctx.fireChannelRead(Unpooled.copiedBuffer("InboundHandler1 " + data.toString(CharsetUtil.UTF_8), CharsetUtil.UTF_8));
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

  

InboundHandler2

查看代码
 public class InboundHandler2 extends ChannelInboundHandlerAdapter {
    private final static Logger log = LoggerFactory.getLogger(InboundHandler2.class);

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
        throws Exception {
        ByteBuf data = (ByteBuf) msg;
        log.info("|InboundHandler2 channelRead 服务端收到数据:" + data.toString(CharsetUtil.UTF_8));
        ctx.writeAndFlush(Unpooled.copiedBuffer("InboundHandler2 " + data.toString(CharsetUtil.UTF_8), CharsetUtil.UTF_8));
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
        throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

 

  • 客户端

客户端启动类

查看代码
bootstrap.group(group)
    .channel(NioSocketChannel.class )
    .remoteAddress(new InetSocketAddress(host, port))
    .option(ChannelOption.TCP_NODELAY, true)
    .handler(new ChannelInitializer() {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ch.pipeline().addLast(new EchoClientHandler());
        }
    });

  

EchoClientHandler 

查看代码
public class EchoClientHandler extends SimpleChannelInboundHandler {
    private final static Logger logger = LoggerFactory.getLogger(EchoServerHandler.class);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        logger.info("Client received: " + msg.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        logger.info("Active" );
        ctx.writeAndFlush(Unpooled.copiedBuffer("echo test..." ,CharsetUtil.UTF_8));
    }


    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        logger.info("EchoClientHandler channelReadComplete" );
        ctx.close();
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

 

执行结果如下:

两个OutboundHandler并没有执行,很奇怪,翻看源码,发现是InboundHandler的使用不对; 

 

pipeline上添加handler最终会往执行下面的流程;

DefaultChannelPipeline#addLast(io.netty.util.concurrent.EventExecutorGroup, java.lang.String, io.netty.channel.ChannelHandler)

 

DefaultChannelPipeline#addLast0

 

 

最终pipeline上的实例如下:

 

如下图:

 

  • 使用pipeline或channel发送数据

 将上面的InboundHandler2的channelRead方法中的发送数据方式改成如下方式:

ctx.pipeline().writeAndFlush(Unpooled.copiedBuffer("|InboundHandler2 " +data.toString(CharsetUtil.UTF_8), CharsetUtil.UTF_8));
ctx.channel().writeAndFlush(Unpooled.copiedBuffer("|InboundHandler2 " +data.toString(CharsetUtil.UTF_8), CharsetUtil.UTF_8));

 

当在InboundHandler执行write方法,执行流程调用链路如下:

io.netty.channel.AbstractChannel#writeAndFlush(java.lang.Object)
 ->io.netty.channel.DefaultChannelPipeline#writeAndFlush(java.lang.Object)
  ->io.netty.channel.AbstractChannelHandlerContext#writeAndFlush(java.lang.Object)
   ->io.netty.channel.AbstractChannelHandlerContext#writeAndFlush(java.lang.Object, io.netty.channel.ChannelPromise)
    ->io.netty.channel.AbstractChannelHandlerContext#findContextOutbound

 

DefaultChannelPipeline#writeAndFlush(java.lang.Object)

 

findContextOutbound会从tail节点往前找OutboundHandler,如下图:

 

最终的执行结果如下:

  • 服务端

  • 客户端

 

  • 使用ctx发送数据的方式

而InboundHandler2的channelRead方法的发送数据是下面这种方式:

ctx.writeAndFlush(xxx);

与pipeline或channel的发送不同的是在执行 io.netty.channel.AbstractChannelHandlerContext#writeAndFlush(java.lang.Object, io.netty.channel.ChannelPromise) 前会执行AbstractChannelHandlerContext#writeAndFlush(java.lang.Object);

此时ctx为InboundHandler2,如下图

 

此时会从ctx往前找OutboundHandler,从pipeline上看ctx往前找是没有的,如下图:

 

如果不想改变InboundHandler2的channelRead方法,可以将服务端的handler添加顺序更改就可以了,如下:

查看代码
serverBootstrap.group(bossGroup, workGroup)
    .channel(NioServerSocketChannel.class)
    .option(ChannelOption.SO_BACKLOG, 1024)
    .childHandler(new ChannelInitializer() {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ch.pipeline().addLast(new OutboundHandler1());
            ch.pipeline().addLast(new OutboundHandler2());
            ch.pipeline().addLast(new InboundHandler1());
            ch.pipeline().addLast(new InboundHandler2());
        }
    });

 

执行效果如下:

  • 服务端

  • 客户端

 

最终handler间的事件传播方向,也就是执行方向,InboundHandler之间传递数据,通过ctx.fireChannelRead(msg)InboundHandler顺序执行,OutboundHandler逆序执行,如下图:

传播的数据也会像包装一样,InboundHandler2会将InboundHandler1的数据打包,传递到OutboundHandler2,再打包,在传递到OutboundHandler1;感觉这个有点像StringBuilder的append方法;

 

posted @ 2021-02-18 17:02  街头卖艺的肖邦  阅读(393)  评论(0编辑  收藏  举报