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

假设maskMASK_CHANNEL_REGISTERED,如果skipFlagsmask&运算是非0,那么while循环会继续,会取下一个上下文。

从方法名findContextInbound可知,是要根据mask查找上下文。如果根据mask要想查找某个上下文,需要怎么做呢?

1) 继承ChannelHandlerAdapter

2) 复写父类中某个方法并且不要设置skip标志。

这段逻辑不好理解,可能描述也没有描述太清楚,希望网友自己在去理会啊!!说了这么多flags,它到到底有什么作用呢?它的最大作用是用于区别InBound/OutBound。不知道大家有没有疑虑,通过接口channel.pipeline().addLast(...),设置流水线,那么netty是否怎么知道,这个handler是用于序列化?还是反序列化呢?其实就是用flags判断。这个也是netty5.0netty4.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的流水线已经完成,有分析不到的位请大家多多指点。




阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 眼睛有飞虫怎么办 面部容易过敏怎么办 皮肤过敏感染怎么办 鼻子容易过敏怎么办 昆虫叮咬红肿怎么办 凤尾竹长虫子怎么办 牙齿里面有牙虫怎么办 家里生锯谷盗怎么办 牙齿蛀虫怎么办 牙齿长蛀虫怎么办 衣柜有蛀虫怎么办 大米里有米虫怎么办 家里到处有米虫怎么办 房间里有米象怎么办 面粉生虫子怎么办 面粉生虫怎么办 富贵竹黄叶怎么办 面粉里有小黑虫怎么办 面粉长虫了怎么办 面里有虫子怎么办 白面生虫子怎么办 米长虫了怎么办 吃死虫子怎么办 木板床潮湿怎么办 家里潮虫多怎么办 平房屋里有潮虫怎么办 家里有小黑飞虫怎么办 床上有虫子怎么办 家里有褥虫怎么办 虫咬皮炎怎么办 蜱虫咬了怎么办 螨虫过敏性鼻炎怎么办 螨虫过敏鼻炎怎么办 螨虫过敏哮喘怎么办 小孩螨虫过敏怎么办 突然螨虫过敏怎么办 螨虫皮肤过敏怎么办啊 螨虫叮咬后很痒怎么办 螨虫叮咬后怎么办 泰迪狗得了螨虫怎么办 得了螨虫怎么办