一、说明
ChannelPipeline的默认实现DefaultChannelPipeline中的双向链表中元素AbstractChannelHandlerContext中有inbound与outbound两个属性,用于标识Context中handler类型。
1、inbound事件与outbound事件,在Pipeline中经过的Context(handler)流向是相反的:
例如,有6个handler:h1(inbound),h2(inhound),h3(inbound)、h4(outbound)、h5(outbound)、h6(outbound),依次调用addList接口添加到ChannelPipeline,添加后,双向链表结构如下图:
- inbound事件(read,只经过inboundhandler):按照上图的上下顺序,从【上】——>向【下】,即:h1,h2,h3
- inbound事件的传播方向是 Head---->custom Tail
- inbound事件都是【通知】事件:即某事件已经发生了,然后通过inbound链进行通知(通常发生在Channel的状态改变(Channel的声明周期中有四种状态:channelUnregistered->channelRegistered->channelInactive->channelActive)或I/O事件就绪时——通常在Unsafe执行某些outbound事件后触发)
- outbound事件(write,只经过outboundhandler):按照上图的上下顺序,从【下】——>向【上】,即:h6,h5,h4
- ountbound事件的传播方向是Tail ----> Head
- outbound事件都是【请求】事件:即触发某事件的发生,然后通过outbound链进行通知(通常是用户主动发起的)
2、addList是往下面添加、addFirst是往上面添加
3、Inbound事件传播方法:
- 用户如果对下面事件感兴趣,可以在自己的Handler中重写这些方法
- 传播流程:参考outbound
方法说明如下:
- 如果调用上图方法,比如fireChannelRegistered(),会发送一个channelRegistered的inount事件给当前ChannelHanlerContext的下一个ChannelHanlerContext(从上面流程图来看,是上一个),(方法参数ChannelHandlerContext即为当前被调用的context(handler))
- 比如在我们自己定义的handler中的某个事件回调函数中,处理完事件后(这个过程通常叫做捕获了一个事件),想让事件继续传播下去的话,需要调用ctx.对应的方法,比如:
4、Outbound事件传播方法:用户如果对下面事件感兴趣,可以在自己的Handler中重写这些方法
- 用户如果对下面事件感兴趣,可以复写对应方法
- 传播流程:当用户调用某个方法,比如Bootstrap的connect方法,会触发一个对应的事件请求(这里是connect事件),进而调用DefaultChannelPipeline的connect方法,而一旦调用DefaultChannelPipeline的connect方法,便会将事件在双向链表中进行流转,对于outbound事件,在链表中是从Tail开始传递的,而tail的connect调用的是AbstractChannelHandlerContext的connect方法,其逻辑是寻找下一个inbound handler然后进行调用,传递期间如果用户没有重写这些方法,会调用ChannelOutboundHandlerAdapter的对应方法,而这个Adapter中,方法实现基本都是调用ctx.对应方法,而这个调用又回到了AbstractChannelHandlerContext中对应方法,而其逻辑是寻找下一个outbound handler然后进行调用,这样一直循环下去,直到事件传递到双向链表的Head节点(connect事件的真正执行,就是在Head这个handler中)
- 如果调用上图方法,比如bind(),会发送一个bind的outbound事件给当前ChannelHanlerContext的下一个ChannelHanlerContext,(方法参数ChannelHandlerContext即为当前被调用的context(handler))
- 其中read、write两个方法都是outbount的?为啥read不是inbound呢?请知道答案的大神给解答下,万分感谢
5、inbound与outbound事件的联系:
b.connect方法调用会触发一个outbound事件,最终事件执行是在Head中调用unsafe的connect方法,unsafe类在我的其他博文中介绍过,这就是一个封装了底层JDK Nio操作的工具类(AbstractNioChannel.AbstractNioUnsafe),在unsafe的connect方法中,先调用doConnect方法进行实际的Socket连接,当链接后会调用【fulfillConnectPromise方法】
可见,在上面方法中,通过调用pipeline().fireChannelActive方法,而这就是inbound事件的起点,在fireXX方法中,将产生一个ChannelActive的inbound事件,而inbound事件,将从Head开始传递,接下来的代码我们就比较熟悉了。
~!!!:与outbound不同的是,outbound链中,如果用户handler不阻断事件的传播的话(也不应该阻断),事件最终是在Head的对应方法中处理,同时Head中对应方法大部分调用的都是unsafe的方法;而inbound链中,如果用户handler不足段事件的传播的话(也不应该阻断),事件最终会在Tail中处理,而我们可以看到Tail的方法实现大部分都是空实现,也就是如果inbound事件,当用户没有实现自定义的处理器并进行处理时,那么netty默认是不处理的。
二、总结
三、编写代码验证Pipeline的事件传播机制
1、服务端代码
三个inbound,三个outbound
package com.mzj.netty.mynettysrc.pipelinetransport;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* @Auther: mazhongjia
* @Date: 2020/9/27 15:14
* @Version: 1.0
*/
public class InboundHandlerA extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// super.channelRead(ctx, msg);
System.out.println("[server]InboundHandlerA....");
ctx.fireChannelRead(msg);
}
}
package com.mzj.netty.mynettysrc.pipelinetransport;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* @Auther: mazhongjia
* @Date: 2020/9/27 15:14
* @Version: 1.0
*/
public class InboundHandlerB extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// super.channelRead(ctx, msg);
System.out.println("[server]InboundHandlerB....");
ctx.fireChannelRead(msg);
}
}
package com.mzj.netty.mynettysrc.pipelinetransport;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* @Auther: mazhongjia
* @Date: 2020/9/27 15:14
* @Version: 1.0
*/
public class InboundHandlerC extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// super.channelRead(ctx, msg);
System.out.println("[server]InboundHandlerC....");
ctx.fireChannelRead(msg);
}
}
package com.mzj.netty.mynettysrc.pipelinetransport;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
/**
* @Auther: mazhongjia
* @Date: 2020/9/27 15:21
* @Version: 1.0
*/
public class OutboundHandlerA extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
// super.write(ctx, msg, promise);
System.out.println("[server]OutboundHandlerA.write");
ctx.write(msg, promise);
}
}
package com.mzj.netty.mynettysrc.pipelinetransport;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import java.util.concurrent.TimeUnit;
/**
* @Auther: mazhongjia
* @Date: 2020/9/27 15:21
* @Version: 1.0
*/
public class OutboundHandlerB extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
// super.write(ctx, msg, promise);
System.out.println("[server]OutboundHandlerB.write");
ctx.write(msg, promise);
}
@Override
public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
ctx.executor().schedule(new Runnable() {
@Override
public void run() {
ctx.channel().write("say hello~~~");
}
}, 3, TimeUnit.SECONDS);
}
}
package com.mzj.netty.mynettysrc.pipelinetransport;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
/**
* @Auther: mazhongjia
* @Date: 2020/9/27 15:21
* @Version: 1.0
*/
public class OutboundHandlerC extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
// super.write(ctx, msg, promise);
System.out.println("[server]OutboundHandlerC.write");
ctx.write(msg, promise);
}
}
服务端6个handler添加顺序为:
- InboundHandlerA
- InboundHandlerB
- InboundHandlerC
- OutboundHandlerA
- OutboundHandlerB
- OutboundHandlerC
package com.mzj.netty.mynettysrc.pipelinetransport;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @Auther: mazhongjia
* @Date: 2020/9/27 15:34
* @Version: 1.0
*/
public class PipelineTestServer {
public void start(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//InboundHandler的执行顺序为注册顺序的正序:A->B->C
ch.pipeline().addLast(new InboundHandlerA());
ch.pipeline().addLast(new InboundHandlerB());
ch.pipeline().addLast(new InboundHandlerC());
//outboundHandler的执行顺序为注册顺序的逆序:C->B->A
ch.pipeline().addLast(new OutboundHandlerA());
ch.pipeline().addLast(new OutboundHandlerB());
ch.pipeline().addLast(new OutboundHandlerC());
}
}).option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = serverBootstrap.bind(port).sync();
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new PipelineTestServer().start(8080);
}
}
服务端实现:
- 当有客户端连接时,触发ChannelInitializer的initChannel方法调用,向NioSocketChannel添加inbound、outbound的handler
- 所有inbound的handler监听channelRead事件,并输出自己被调用
- 所有outbound的handler监听write事件,并输出自己被调用
- OutboundHandlerB这个handler添加成功时(在有客户端连接成功时向childHandler中添加的),调用ctx.executor并在3秒后,向服务端发送say hello~~~~~字符串
2、客户端代码
package com.mzj.netty.mynettysrc.pipelinetransport;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* @Auther: mazhongjia
* @Date: 2020/9/27 16:41
* @Version: 1.0
*/
public class PipelineTestClient {
public void connect(String host, int port) throws InterruptedException {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ClientIntHandler());
}
});
ChannelFuture future = bootstrap.connect(host, port).sync();
future.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
PipelineTestClient client = new PipelineTestClient();
client.connect("127.0.0.1", 8080);
}
private static class ClientIntHandler extends ChannelInboundHandlerAdapter {
//读取服务端信息
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("[client]ClientIntHandler.channelRead");
ByteBuf result = (ByteBuf) msg;
byte[] resultBytes = new byte[result.readableBytes()];
result.readBytes(resultBytes);
result.release();
ctx.close();
System.out.println("[client]Server said : " + new String(resultBytes));
super.channelRead(ctx, msg);
}
//当连接建立的时候向服务端发送消息,channelActive事件在连接建立的时候会被触发
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("[client]ClientIntHandler active......");
String msg = "Are you ok?";
byte[] messageBytes = msg.getBytes();
ByteBuf byteBuf = ctx.alloc().buffer(messageBytes.length);
byteBuf.writeBytes(messageBytes);
ctx.write(byteBuf);
ctx.flush();
}
}
}
- 客户端实现:
- 当客户端active时,向服务端发送Are you ok?字符串
- 并监听服务端发送的数据,进行System.out.println
3、测试(只关注服务端输出)
先后运行服务端、客户端,在客户端连接成功时:
- 服务端:向客户端NioSocketChannel添加handler时,在OutboundHandlerB添加后,3秒后向客户端发送信息
- 客户端:连接成功后向服务端发送数据
- 服务端:收到客户端发送数据后,经过3个inbound handler的channelRead事件回调函数,首先输出:
- 服务端:3秒后,在InboundHandlerB中,向客户端发送数据
- 服务端:经过3个outbound handler的write事件回调函数,又输出: