Netty源码分析之流水线

上一篇分析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(),然后调用下面几个方法:

@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的流水线已经完成,有分析不到的位请大家多多指点。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值