Netty源码分析之流水线
来源:互联网 发布:云计算好处 编辑:程序博客网 时间:2024/05/16 09:52
上一篇分析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(),然后调用下面几个方法:
@Overridepublic 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的流水线已经完成,有分析不到的位请大家多多指点。