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指代),可采取下面的方式处理:
- 不调用父类的channelXXX方法;
- 不调用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方法;