Netty源码深度解析-Pipeline(2) 以客户端为例分析Pipeline工作原理

导读

原创文章,转载请注明出处。

本文源码地址:netty-source-code-analysis

本文所使用的netty版本4.1.6.Final:带注释的netty源码

在"Pipeline的构造"这一节中我们已经讲过了,Pipeline中利用了责任链模式,而发挥责任链功能的数据结构就是由多个HandlerContext构成的的双向链表,而每一个HandlerContext又对应一个ChannelHandler(组合模式或者继承)。由于每一个HandlerContext对应一个ChannelHandler,所以本文行文中有时候会把HandlerContextChannelHandler等同对待,例如文章中

从当前HandlerContexttail)开始向head方向遍历寻找下一个ChannelOutboundHandler

实际上准确来说应该是

从当前HandlerContexttail)开始向head方向遍历寻找下一个HandlerContext,并且该HandlerContext包含或者实现了ChannelOutboundHandler

为了行文不那么拗口,我们就把HandlerContextChannelHandler等同对待了。

本文我们以客户端建立连接、收发数据为例来学习一下Pipeline的工作原理。

1 ChannelHandler方法概览

ChannelPipeline的构造这篇文章中我们提到过ChannelHandlerPipeline中双向链表由HandlerContext组成,而每一个HandlerContext又包含一个ChannleHandler。我们看一下其中的主要方法,这是所有ChannelHandler的公共接口,公共接口中主要有两个方法handlerAddedhandlerRemoved,这是在ChannelHandler添加和删除完成之后的回调方法。上一篇文章中我们已经分析过PipelineChannelHandler的添加和删除了,这里不再赘述。

public interface ChannelHandler {
    void handlerAdded(ChannelHandlerContext ctx) throws Exception;
    void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
     @Deprecated
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
}

我们重点来看ChannelHandler的两个子接口,ChannelInboundHandlerChannelOutboundHandler
首先来看ChannelInboundHandlerChannelInboundHandler类上的注释如下

which adds callbacks for state changes. This allows the user
to hook in to state changes easily.

翻译过来就是“Channel状态改变时的回调方法”。其中的方法名多为Channel + 动词过去分词的形式。

public interface ChannelInboundHandler extends ChannelHandler {
    void channelRegistered(ChannelHandlerContext ctx) throws Exception;
    void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
    void channelActive(ChannelHandlerContext ctx) throws Exception;
    void channelInactive(ChannelHandlerContext ctx) throws Exception;
    void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
    void channelReadComplete(ChannelHandlerContext ctx) throws Exception;
    void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
    void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}

再来看ChannelOutboundHandlerChannelOutboundHandler上的注释如下

which will get notified for IO-outbound-operations.

翻译过来就是“当发生IO出站操作时的回调方法”,其中的方法名多为动词原型的形式。

public interface ChannelOutboundHandler extends ChannelHandler {
    void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;
    void connect(
            ChannelHandlerContext ctx, SocketAddress remoteAddress,
            SocketAddress localAddress, ChannelPromise promise) throws Exception;
    void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
    void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
    void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
    void read(ChannelHandlerContext ctx) throws Exception;
    void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;
    void flush(ChannelHandlerContext ctx) throws Exception;
}

2 ChannelInboundInvoker和ChannelOutboundInvoker

和本文相关的组件中,ChannelPipelineChannelHandlerContext都实现了ChannelInboundInvokerChannelOutboundInvoker接口。

ChannelInboundInvoker和ChannelOutboundInvoker UML类图

ChannelInboundInvoker内的方法的特点是大部分都是以fire开头,并且是fire + 动词的过去分词的形式。我们知道过去分词是表被动的意思,所以这里呢,一般是Channel里发生了某些“被动事件”之后调用的。

public interface ChannelInboundInvoker {
    ChannelInboundInvoker fireChannelRegistered();
    ChannelInboundInvoker fireChannelUnregistered();
    ChannelInboundInvoker fireChannelActive();
    ChannelInboundInvoker fireChannelInactive();
    ChannelInboundInvoker fireExceptionCaught(Throwable cause);
    ChannelInboundInvoker fireUserEventTriggered(Object event);
    ChannelInboundInvoker fireChannelRead(Object msg);
    ChannelInboundInvoker fireChannelReadComplete();
    ChannelInboundInvoker fireChannelWritabilityChanged();
}

ChannelOutboundInvoker内的方法的特点是除了后边几个以new开头的方法之外,都是以单个或者两个动词原型的形式作为方法名称,比如readwritewriteAndFlush。这里直接用动词原型就是表示主动的意思,所以这里一般是向Channel发送主动命令使用的。

public interface ChannelOutboundInvoker {
    ChannelFuture bind(SocketAddress localAddress);
    ChannelFuture connect(SocketAddress remoteAddress);
    ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress);
    ChannelFuture disconnect();
    ChannelFuture close();
    ChannelFuture deregister();
    ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise);
    ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise);
    ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
    ChannelFuture disconnect(ChannelPromise promise);
    ChannelFuture close(ChannelPromise promise);
    ChannelFuture deregister(ChannelPromise promise);
    ChannelOutboundInvoker read();
    ChannelFuture write(Object msg);
    ChannelFuture write(Object msg, ChannelPromise promise);
    ChannelOutboundInvoker flush();
    ChannelFuture writeAndFlush(Object msg, ChannelPromise promise);
    ChannelFuture writeAndFlush(Object msg);
    ChannelPromise newPromise();
    ChannelProgressivePromise newProgressivePromise();
    ChannelFuture newSucceededFuture();
    ChannelFuture newFailedFuture(Throwable cause);
    ChannelPromise voidPromise();

我们来仔细分析一下上文中提到的ChannelInboundHandlerChannelInboundInvokerChannelOutboundHandlerChannelOutboundInvoker
我们看到ChannelInboundHandlerChannelInboundInvoker中的方法非常相似,ChannelInboundHandler中的方法名多以Channel加动词过去分词的形式组成,而ChannelInboundInvoker中的方法就是ChannelInboundHandler中的方法加上fire构成。

ChannelOutboundHandlerChannelOutboundInvoker中的方法名几乎一模一样,都是以动词原形作为方法名。

我们来仔细琢磨一下这些方法的命名,Channel加动词过去分词表示该Channel发生了某种事件,而fireChannel加动词过去分词表示在该Channel上触发这种事件。例如ChannelRead表示该Channel读到了数据事件,fireChannelRead表示在该Channel上触发ChannelRead事件。

直接用动词原形作为方法名,表示命令该Channel进行某种操作,例如read表示命令该Channel进行数据的读取。为什么ChannelOutboundInvoker中的方法名没有以fire开头呢,因为ChannelOutbound传播的不是事件,而是一种主动的操作。

还有同学记得我在“Netty整体架构”这篇文章中提到的“事件”和“命令”吗,在某些地方翻译成“入站事件”和“出站事件”,我个人认为翻译成“事件”和“命令”更容易理解,因为我们感知到“事件”的发生是被动的,而发出“命令”是主动的,都翻译成“事件”不容易理解。

3 以客户端为例分析Pipeline工作原理

本文的示例代码在package com.zhongdaima.netty.analysis.pipeline中。
服务端示例代码如下,这个服务端的功能就是将所有客户端发过来的数据原封不动地返回。

/**
 * 欢迎关注公众号“种代码“,获取博主微信深入交流
 *
 * @author wangjianxin
 */
public class ServerBoot {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(1);
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .childHandler(new ChannelInitializer() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    //连接发过来的数据原样返回
                                    if (msg instanceof ByteBuf) {
                                        ctx.write(msg);
                                        ctx.flush();
                                    }
                                }

                            });
                        }
                    });
            ChannelFuture f = b.bind(8000).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

客户端的示例代码如下,在客户端中我们添加了3个ChannelHandler,分别是ABC

/**
 * 欢迎关注公众号“种代码“,获取博主微信深入交流
 *
 * @author wangjianxin
 */
public class ClientBoot {
    public static void main(String[] args) {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ch.pipeline().addLast("A", new AInBoundHandler());
                            ch.pipeline().addLast("B", new BOutBoundHandler());
                            ch.pipeline().addLast("C", new CDuplexHandler());
                        }
                    });
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8000);
            Channel channel = channelFuture.syncUninterruptibly().channel();
            channel.write(Unpooled.wrappedBuffer("Hello, 种代码".getBytes()));
            channel.flush();
            channel.closeFuture().awaitUninterruptibly();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}

AInboundHandler继承自ChannelInboundHandlerAdapterChannelInboundHandlerAdapterNettyChannelInboundHandler的默认实现,AInboundHandler是一个ChannelInboundHandler

public class AInboundHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("ChannelActive in A");
        super.channelActive(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof ByteBuf) {
            byte[] bytes = new byte[((ByteBuf) msg).readableBytes()];
            //这里调用getBytes不会导致readerIndex的移动
            ((ByteBuf) msg).getBytes(0, bytes);
            System.out.println("ChannelRead in A, msg=" + new String(bytes));
        }
        super.channelRead(ctx, msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("ExceptionCaught in A, cause=" + cause);
        super.exceptionCaught(ctx, cause);
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("HandlerAdded in A");
        super.handlerAdded(ctx);
    }
}

BOutboundHandler继承自ChannelOutboundHandlerAdapterChannelOutboundHandlerAdapter是netty中``ChannelOutboundHandler的默认实现,BOutboundHandler是一个ChannelOutboundHandler`。

public class BOutboundHandler extends ChannelOutboundHandlerAdapter {
    @Override
    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
        System.out.println("Connect in B");
        super.connect(ctx, remoteAddress, localAddress, promise);
    }

    @Override
    public void read(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Read in B");
        super.read(ctx);
    }

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        if (msg instanceof ByteBuf){
            byte[] bytes = new byte[((ByteBuf) msg).readableBytes()];
            //这里调用getBytes不会导致readerIndex的移动
            ((ByteBuf) msg).getBytes(0, bytes);
            System.out.println("Write in B, msg=" + new String(bytes));
        }
        super.write(ctx, msg, promise);
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("HandlerAdded in B");
        super.handlerAdded(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("ExceptionCaught in B, cause=" + cause);
        super.exceptionCaught(ctx, cause);
    }
}

CDuplexHandler继承自ChannelDuplexHandlerChannelDuplexHandler同时实现了ChannelInboundHandlerChannelOutboundHandler,所以CDuplexHandler既是一个ChannelInboundHandler又是一个ChannelOutboundHandler

public class CDuplexHandler extends ChannelDuplexHandler {
    @Override
    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
        System.out.println("Connect in C");
        super.connect(ctx, remoteAddress, localAddress, promise);
    }

    @Override
    public void read(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Read in C");
        super.read(ctx);
    }

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        if (msg instanceof ByteBuf) {
            byte[] bytes = new byte[((ByteBuf) msg).readableBytes()];
            //这里调用getBytes不会导致readerIndex的移动
            ((ByteBuf) msg).getBytes(0, bytes);
            System.out.println("Write in C, msg=" + new String(bytes));
        }
        super.write(ctx, msg, promise);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("ChannelActive in C");
        super.channelActive(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof ByteBuf) {
            byte[] bytes = new byte[((ByteBuf) msg).readableBytes()];
            //这里调用getBytes不会导致readerIndex的移动
            ((ByteBuf) msg).getBytes(0, bytes);
            System.out.println("ChannelRead in C, msg=" + new String(bytes));
        }
        super.channelRead(ctx, msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("ExceptionCaught in C, cause=" + cause);
        super.exceptionCaught(ctx, cause);
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("HandlerAdded in C");
        super.handlerAdded(ctx);
    }
}

在添加完这3个Handler之后,我们来看一下客户端的Pipeline状态,当前Pipeline的状态如下图所示。除了内置的HeadContextTailContext之外,还有我们手动添加的ABC3个Handler

当前客户端的Pipeline状态

先启动服务端,再启动客户端,我们可以在客户端的控制台中看到如下输出。

Connect in C
Connect in B
ChannelActive in A
ChannelActive in C
Read in C
Read in B
Write in C, msg=Hello, 种代码
Write in B, msg=Hello, 种代码
ChannelRead in A, msg=Hello, 种代码
ChannelRead in C, msg=Hello, 种代码
Read in C
Read in B

4 ChannelPipeline的方向

我们在Pipeline的构造中提到过Pipeline中有两个特殊的ChannelHandlerContext,分别是HeadContextTailContext。既然有了HeadTail那必然是有方向了,那Pipeline的方向是什么样的呢,我们看下面这张图。

image.png

Channel直接接触的方向是Head,另一端是Tail
在“Netty整体架构”这篇文章中也提到过ChannelInboundHandlerChannelOutboundHandler,那InboundOutbound又是哪个方向呢。我们站在应用的位置来看,即站在BizHandler的位置来看,从Channel读取数据进入到我们的应用即是Inbound,而从我们的应用向Channel写数据即是Outbound

5 Pipeline的工作原理

我们在前面的文章中在牵涉到Pipeline的地方都一笔带过了,今天咱们来详细分析一下曾经略过的内容。

5.1 connect的传播

在我们调用bootstrap.connect("127.0.0.1", 8000)这行代码进行连接时最终会调用到AbstractChannel#connect方法,该方法逻辑很简单,直接调用了pipelineconnect方法,咱们跟进去看。

@Override
public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
    return pipeline.connect(remoteAddress, promise);
}

Pipelineconnect方法同样很简单,调用了tailconnect方法,TailContext继承自AbstractChannelHandlerContext,并没有覆盖connect方法,所以tail.connect调用到了AbstractChannelHandlerContext中的connect方法。

public final ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
    return tail.connect(remoteAddress, promise);
}

AbstractChannelHandlerContext中的connect方法,先是调用findContextOutbound,找到下一个ChannelOutboundHandler,这个方法很简单,就是从当前HandlerContexttail)开始向head方向遍历寻找下一个ChannelOutboundHandler,在我们的示例中,tail的下一个ChannelOutboundHandler就是我们所添加的CDuplexHandler,找到之后调用了next.invokeContext方法。

@Override
public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
    return connect(remoteAddress, null, promise);
}

@Override
public ChannelFuture connect(
        final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
    //从当前HandlerContext向`head`方向查找下一个包含`ChannelOutboundHandler`的`HandlerContext`
    final AbstractChannelHandlerContext next = findContextOutbound();
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeConnect(remoteAddress, localAddress, promise);
    } else {
        safeExecute(executor, new Runnable() {
            @Override
            public void run() {
                next.invokeConnect(remoteAddress, localAddress, promise);
            }
        }, promise, null);
    }
    return promise;
}

invokeConnect方法首先调用invokeHandler()方法判断当前Handler是否已经添加完成(当前HandlerhandlerAdded方法已经被调用过)。如果返回true就调用当前Handlerconnect方法,这就是我们的CDuplexHandlerconnect方法被调用到的地方,也就是我们在控制台看到的第一个输出Connect in C。如果返回false就继续调用HandlerContextconnect方法继续查找下一个ChannelOutboundHanlder

private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
    //判断当前Handler是否已经完成添加(即handlerAdded方法被调用过了)
    if (invokeHandler()) {
        try {
            ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
        } catch (Throwable t) {
            notifyOutboundHandlerException(t, promise);
        }
    } else {
        connect(remoteAddress, localAddress, promise);
    }
}

回到咱们的CDuplexHandlerconnect方法,打印完Connect in C之后调用了super.connect方法,即ChannelDuplexHandler#connect方法。

public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
    System.out.println("Connect in C");
    super.connect(ctx, remoteAddress, localAddress, promise);
}

ChannelDuplexHandler#connect方法调用了它所在的HandlerContextconnect方法,即AbstractChannelHandlerContext#connect方法,熟悉的身影又回来了,咱们刚刚分析过这个方法,即tail.connect,而TailContext未覆盖这个方法,tail.connect就是ChannelDuplexHandler#connect

public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
                    SocketAddress localAddress, ChannelPromise promise) throws Exception {
    ctx.connect(remoteAddress, localAddress, promise);
}

显而易见下一个ChannelOutboundHander就是BOutboundHandler,所以我们看到控制台的第二行输出Connect in B
最后一个ChannelOutboundHandlerHeadContext,我们到HeadContext来看一下它的connnect方法。HeadContextconnect方法很简单,直接调用了unsafeconnect方法,而unsafe.connect方法我们在“客户端的启动过程”这篇文章中已经分析过了,不再赘述。

@Override
public void connect(
        ChannelHandlerContext ctx,
        SocketAddress remoteAddress, SocketAddress localAddress,
        ChannelPromise promise) throws Exception {
    unsafe.connect(remoteAddress, localAddress, promise);
}

5.2 ChannelActive的传播

我们在“客户端的启动过程”这篇文章中提到过AbstractNioUnsafe#finishConnect方法,当NioEventLoop检测到Channel上有OP_CONNECT事件发生时,会调用unsafe.finishConnect方法。

if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
    // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
    // See https://github.com/netty/netty/issues/924
    int ops = k.interestOps();
    ops &= ~SelectionKey.OP_CONNECT;
    k.interestOps(ops);

    unsafe.finishConnect();
}

finishConnect方法在AbstratNioUnsafe中。在调用完doFinishConnect方法之后会调用fulfillConnectPromise方法,我们一起来看一下。

@Override
public final void finishConnect() {
    try {
        boolean wasActive = isActive();
        doFinishConnect();
        fulfillConnectPromise(connectPromise, wasActive);
    } catch (Throwable t) {

    } finally {

    }
}

fulfillConnectPromise方法里有很多内容,这里咱们只关注本次要讲的重点,pipeline().fireChannelActive()

private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {

    if (!wasActive && active) {
        pipeline().fireChannelActive();
    }

}

我们看一下pipelinefireChannelActive方法,这里调用了invokeChannelActive方法,传递的参数是HeadContext

@Override
public final ChannelPipeline fireChannelActive() {
    AbstractChannelHandlerContext.invokeChannelActive(head);
    return this;
}

invokeChannelActive方法调用了next.invokeChannelActive,这里的next就是HeadContext

static void invokeChannelActive(final AbstractChannelHandlerContext next) {
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeChannelActive();
    } else {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                next.invokeChannelActive();
            }
        });
    }
}

invokeChannelActive方法调用了HandlerchannelActive方法,这里的Handler自然也是HeadContext

private void invokeChannelActive() {
    if (invokeHandler()) {
        try {
            ((ChannelInboundHandler) handler()).channelActive(this);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    } else {
        fireChannelActive();
    }
}

HeadContext里的channelActive方法调用了ct.fireChannelActive(),显然这里的ctx就是HeadContext本身。

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    ctx.fireChannelActive();

    readIfIsAutoRead();
}

fireChannelActive方法在AbstractChannelHandlerContext中,它首先从当前HandlerContext查找下一个ChannelInboundHandler,然后调用invokeChannelActive方法,熟悉的身影又回来了,invokeChannelActive方法咱们刚刚也见过。刚才调用invokeChannelActive方法是由Pipeline发起的,直接传递了head作为参数,而这里是由head发起的,参数是从head开始的下一个包含ChannelInboundHandler,显然这个参数是我们所添加的AInboundHandler,所以我们在控制台看到ChannelActive in A

@Override
public ChannelHandlerContext fireChannelActive() {
    invokeChannelActive(findContextInbound());
    return this;
}

继续下去可以想见AInboundHandler被调用后也会查找下一个ChannelInboundHandler就是CDuplexHandler,所以我们在控制台看到的下一条输出就是ChannelActive in C

5.3 read的传播

刚才咱们在看HeadContext里的channelActive方法时,其中并不仅仅只有ctx.fireChannelActive()调用,紧跟着的就是readIfIsAutoRead()的调用。咱们一起来看一下。这里判断channel是否配置了自动读取,默认情况下是true,接着调用了channel.read()

private void readIfIsAutoRead() {
    if (channel.config().isAutoRead()) {
        channel.read();
    }
}

channel.read方法在AbstractChannel中,调用了pipeline.read方法。

@Override
public Channel read() {
    pipeline.read();
    return this;
}

pipelineread方法调用了tail.read,这里的tail就是指TailContext了。

@Override
public final ChannelPipeline read() {
    tail.read();
    return this;
}

tail没有覆盖read方法,这个方法的实现在AbstractChannelHandlerContext中,首先调用findContextOutbound方法查找下一个ChannelOutboundHandler。第一个查找到的就是CDuplexHandler,调用了它的invokeRead方法。

@Override
public ChannelHandlerContext read() {
    final AbstractChannelHandlerContext next = findContextOutbound();
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeRead();
    } else {
        Runnable task = next.invokeReadTask;
        if (task == null) {
            next.invokeReadTask = task = new Runnable() {
                @Override
                public void run() {
                    next.invokeRead();
                }
            };
        }
        executor.execute(task);
    }

    return this;
}

invokeRead方法在AbstractChannelHandlerContext中,invokeRead调用了它所包含的ChannelInboundHandlerchannelRead方法,即CDuplexHandlerchannelRead方法,在控制台打印出Read in C

private void invokeChannelRead(Object msg) {
    if (invokeHandler()) {
        try {
            ((ChannelInboundHandler) handler()).channelRead(this, msg);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    } else {
        fireChannelRead(msg);
    }
}

CDuplexHandlerchannelRead方法在打印完成后调用了super.read(ctx),我们跟下去看一下。

@Override
public void read(ChannelHandlerContext ctx) throws Exception {
    System.out.println("Read in C");
    super.read(ctx);
}

接着调用了ctx.read(),即AbstractChannelHandlerContext#read方法,熟悉的方法又出现了,接着调用下去就到了下一个ChannelOutboundHandlerBOutboundHandler,在控制台打印出Read in B

@Override
public void read(ChannelHandlerContext ctx) throws Exception {
    ctx.read();
}

接着往下调用就到了HeadContextHeadContextread方法调用了unsafe.beginRead

@Override
public void read(ChannelHandlerContext ctx) {
    unsafe.beginRead();
}

unsafebeginRead方法调用了doBeginRead

@Override
public final void beginRead() {
    assertEventLoop();

    if (!isActive()) {
        return;
    }

    try {
        doBeginRead();
    } catch (final Exception e) {
        invokeLater(new Runnable() {
            @Override
            public void run() {
                pipeline.fireExceptionCaught(e);
            }
        });
        close(voidPromise());
    }
}

我们以AbstractNioChannel为例看一下doBeginRead方法。这里的doBeginRead方法把readInterestOp加入到兴趣事件中,readInterestOp在构造方法中被赋值,即是OP_READ兴趣事件。

    @Override
    protected void doBeginRead() throws Exception {
        final int interestOps = selectionKey.interestOps();
        if ((interestOps & readInterestOp) == 0) {
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }

调用read方法,并不能真正从Channel中读取数据,只是把OP_READ兴趣事件加入到selectionKey

5.4 write的传播

在完成连接后,我们在客户端代码中调用了channel.write(Unpooled.wrappedBuffer("Hello, 种代码".getBytes()));向服务端发送数据。咱们跟进去看看,这里的channel.write方法在AbstractChannel中,直接调用了pipeline.write方法。

@Override
public ChannelFuture write(Object msg) {
    return pipeline.write(msg);
}

pipelinewrite方法调用了tail.write方法,tail即是TailContext

@Override
public final ChannelFuture write(Object msg) {
    return tail.write(msg);
}

咱们就不再一步步跟下去了,从控制台打印出的Write in C, msg=Hello, 种代码Write in B, msg=Hello, 种代码来推测,write命令从tail开始经过了CDuplexHandlerBOutboundHandler最后到达HeadContext

5.5 ChannelRead的传播

在服务端接收到数据之后,直接将数据原样返回,当Eventloop发现Channel中发生OP_READ事件时,调用unsafe.read()进行数据的读取。这部分代码咱们在讲EventLoop的时候已经提到过,在NioEventLoopprocessSelectedKey方法中。

if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
    unsafe.read();
}

咱们跟进去看看unsafe.read方法,方法实现在NioByteUnsafe中,其中分配缓冲区读取数据的过程咱们先略过,这篇文章咱们着重介绍Pipeline,直接看读取数据之后调用了什么方法。在读取数据之后(所分配的缓冲区填满)调用了pipeline.fireChannelRead(byteBuf),而在读完数据之后(tcp缓冲区数据被读完)调用了pipeline.fireChannelReadComplete()

 @Override
    public final void read() {

        try {
            do {
                //分配缓冲区
                byteBuf = allocHandle.allocate(allocator);
                //读取数据
                allocHandle.lastBytesRead(doReadBytes(byteBuf));
                //触发ChannelRead事件
                pipeline.fireChannelRead(byteBuf);
                
            } while (allocHandle.continueReading());
            //触发ChannelReadComplete事件
            pipeline.fireChannelReadComplete();

        } catch (Throwable t) {

        } finally {

        }
    }
}

先看看``pipeline.fireChannelRead(byteBuf)方法,很简单,这里调用了AbstractChannelHandlerContext.invokeChannelRead方法,并传入参数head,即是HeadContext`。

@Override
public final ChannelPipeline fireChannelRead(Object msg) {
    AbstractChannelHandlerContext.invokeChannelRead(head, msg);
    return this;
}

invokeChannelRead方法又调用了next.invokeChannelRead,这里的nextHeadContext

static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeChannelRead(m);
    } else {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                next.invokeChannelRead(m);
            }
        });
    }
}

到这儿里咱们也不再一步步跟下去了,根据控制台打印出的ChannelRead in A, msg=Hello, 种代码ChannelRead in C, msg=Hello, 种代码来推测,ChannelRead事件从HeadContext开始经过AInboundHandler,和CDuplexHandler到达TailContext
最后在控制台又打印出“Read in C”和“Read in B”是怎么回事呢,那是因为在pipeline.fireChannelReadComplete()又发出了read命令,咱们不再赘述。

6 总结

  • ChannelOutboundInvoker:用来向Channel发送“命令”的接口。
  • ChannelInboundInvoker:用来触发Channel上发生“事件”的接口。
  • ChannelOutboundHandler:“命令”在Pipeline上传播时的回调方法。
  • ChannelInboundHandler: “事件”在Pipeline上传播时的回调方法。
  • Pipeline中主动发出的命令,例如connectreadwrite等由TailContext开始依次经过所有的ChannelOutboundHandler,最后到达HeadContext
  • Pipeline中被动发生的事件,例如ChannelActiveChannelRead等由HeadContext开始依次经过所有的ChannelInboundHandler,最后到达TailContext

关于作者

王建新,转转架构部资深Java工程师,主要负责服务治理、RPC框架、分布式调用跟踪、监控系统等。爱技术、爱学习,欢迎联系交流。

你可能感兴趣的:(Netty源码深度解析-Pipeline(2) 以客户端为例分析Pipeline工作原理)