上一篇分析Netty线程模型,今天分析Netty另外一个重点流水线Pipe。
一、流水线处理逻辑
Netty把各个事件放到Pipe中,进行自动化处理,这个做法非常棒!!思想非常独特,使用者不需要关心是哪个事件?怎么处理?用户只需要把ChannelHandler设置到Pipe中就行了。下图是Netty流水线经典处理流程:
左侧是接收到的消息处理流程,右侧是发送的消息处理流程。Netty流水线实现方式很简单,就是双向链表(非循环链表)。它把所有的ChannelHandler放到链表中,如果是read事件,它从链表头开始处理,如果是write事件,它从链表尾开处理。
二、流水线Flags
流水线Pipe不需要使用者创建,这部分对使用者来说,完全是透明的。那么在什么地方创建的呢?
protected AbstractChannel(Channel parent, EventLoop eventLoop) {
this.parent = parent;
this.eventLoop = validate(eventLoop);
unsafe = newUnsafe();
pipeline = new DefaultChannelPipeline(this);
}
由上面代码可知,流水线是在创建Channel的时候,new出来的Pipe。下面先看两个函数,这个两个函数逻辑比较绕,首先是skipFlags0方法
private static int skipFlags0(Class<? extends ChannelHandler> handlerType) {
int flags = 0;
try {
if (handlerType.getMethod(
"handlerAdded", ChannelHandlerContext.class).isAnnotationPresent(Skip.class)) {
flags |= MASK_HANDLER_ADDED;
}
if (handlerType.getMethod(
"handlerRemoved", ChannelHandlerContext.class).isAnnotationPresent(Skip.class)) {
flags |= MASK_HANDLER_REMOVED;
}
...
} catch (Exception e) {
// Should never reach here.
PlatformDependent.throwException(e);
}
return flags;
}
这个里面大部分都是if判断,主要检查某个方法是否存在,并且是否设置了skip(注解),才会设置flags bit位置。我们从反方向考虑:ChannelHandlerAdapter父类这些方法都已经实现并且都存在skip注解,那么要是想某个方法不进入这个if分支,该如何做呢?
1)继承ChannelHandlerAdapter
2)复写父类中某个方法并且不要设置skip标志。
第二个方法,是findContextInbound/findContextOutbound
public ChannelHandlerContext fireChannelRegistered() {
DefaultChannelHandlerContext next = findContextInbound(MASK_CHANNEL_REGISTERED);
next.invoker.invokeChannelRegistered(next);
return this;
}
private DefaultChannelHandlerContext findContextInbound(int mask) {
DefaultChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while ((ctx.skipFlags & mask) != 0);
return ctx;
}
最绕人的逻辑就是 (ctx.skipflags & mask) != 0。
假设mask是MASK_CHANNEL_REGISTERED,如果skipFlags中mask则&运算是非0,那么while循环会继续,会取下一个上下文。
从方法名findContextInbound可知,是要根据mask查找上下文。如果根据mask要想查找某个上下文,需要怎么做呢?
1) 继承ChannelHandlerAdapter
2) 复写父类中某个方法并且不要设置skip标志。
这段逻辑不好理解,可能描述也没有描述太清楚,希望网友自己在去理会啊!!说了这么多flags,它到到底有什么作用呢?它的最大作用是用于区别InBound/OutBound。不知道大家有没有疑虑,通过接口channel.pipeline().addLast(...),设置流水线,那么netty是否怎么知道,这个handler是用于序列化?还是反序列化呢?其实就是用flags判断。这个也是netty5.0和netty4.0最大的区别。
三、流水线动态添加
Netty流水线中ChannelHandler是支持动态插入、删除的。这个特点在《Netty权威指南》中也有提到。接下来分析一下动态插入,首先看一下代码。
public void bind(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
protected void initChannel(SocketChannel ch) throws Exception {
// 将监听事件 注册到ChannelPipe流水线中 放到链表中 也可以注册多个监听事件 可以指定名字如果没有名字 会自动生成
ch.pipeline().addLast("GetTime", new TimeServerHandler());
}
}
由上面代码可知,在启动服务的时候,设置了一个内部类ChildChannelHandler。该类主要用于将解码器,添加到流水线中。最开始我认为,服务启动之后,initChannel方法就会调用,其实不然,initChannel方法是在客户端与服务建链成功之后才会被调用到。那么是在具体什么代码中调用到呢?
我们在上一篇介绍线程模型时提到了register0方法,在该方法中有一行这样的代码pipeline.fireChannelRegistered(),然后调用下面几个方法:
@Override
public ChannelHandlerContext fireChannelRegistered() {
DefaultChannelHandlerContext next = findContextInbound(MASK_CHANNEL_REGISTERED);
next.invoker.invokeChannelRegistered(next);
return this;
}
ChannelInitializer.java中的
public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
ChannelPipeline pipeline = ctx.pipeline();
boolean success = false;
try {
initChannel((C) ctx.channel());
pipeline.remove(this);
ctx.fireChannelRegistered();
success = true;
} catch (Throwable t) {
logger.warn("Failed to initialize a channel. Closing: " + ctx.channel(), t);
} finally {
if (pipeline.context(this) != null) {
pipeline.remove(this);
}
if (!success) {
ctx.close();
}
}
}
在这个方法中有initChannel((C)ctx.channel());这段代码就会调用到上面initChannel方法,然后在流水线中动态添加解码器。
通过上面代码,有一个地方需要再详细介绍一下,在查找ChannelHandleContext上下文,是通过MASK_CHANNEL_REGISTERED,那么在什么地方设置的这个标志呢?
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
protected void initChannel(SocketChannel ch) throws Exception {
// 将监听事件 注册到ChannelPipe流水线中 放到链表中 也可以注册多个监听事件 可以指定名字如果没有名字 会自动生成
ch.pipeline().addLast("GetTime", new TimeServerHandler());
}
}
在设置childHandler时候定义了一个内部私有类,这个类继承了ChannelInitializer。父类ChannelInitializer覆写了方法channelRegistered,该方法就是MASK_CHANNEL_REGISTERED。
至此,分析Netty的流水线已经完成,有分析不到的位请大家多多指点。