美文网首页Java 杂谈java
第十九节 netty源码分析之 pipleline和handl

第十九节 netty源码分析之 pipleline和handl

作者: 勃列日涅夫 | 来源:发表于2019-02-13 14:15 被阅读0次

pipleline和handler以及pipeline的数据流向

  • 先理一下思路。首先我们考虑到之前的文章分析,没创建一个channel就会创建一个pipeline与之对应。每个pipeline会有AbstractChannelHandlerContext属性的tail和head从而组成要给双向链表。那么pipeline的handler添加和数据流向其实都是基于HandlerContext和双向链表的性质。下面具体分析。

当然我们仍然下面这段代码分析,主要分析pipeline的添加

b.group(group)
            //初始化工厂ReflectiveChannelFactory为后续链接connect方法创建NioSocketChannel对象
             .channel(NioSocketChannel.class)
                    //将选项添加到AbstractBootstrap属性options. 实现类中Bootstrap的init(Channel channel)方法设置channel的类型
             .option(ChannelOption.TCP_NODELAY, true)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(new EchoClientHandler());
                 }
             });

pipeline.addXXX 都有一个重载的方法, 例如 addLast, 它有一个重载的版本
直接查看DefaultChannelPipeline源码:

 public final ChannelPipeline addLast(ChannelHandler... handlers) {
        return addLast(null, handlers);
    }

我们一路查看下去,找到重载的方法,且记住我们入参里group、和name都是null

 @Override
    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            //检查是否重复添加
            checkMultiplicity(handler);
//创建DefaultChannelHandlerContext对象
            newCtx = newContext(group, filterName(name, handler), handler);
//            将生成的newCtx插入handlercontex链表中
            addLast0(newCtx);

            // If the registered is false it means that the channel was not registered on an eventloop yet.
            // In this case we add the context to the pipeline and add a task that will call
            // ChannelHandler.handlerAdded(...) once the channel is registered.
            if (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }

            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                newCtx.setAddPending();
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }

从addLast0方法看到,这里是将我们的handler添加到了tail的前面

private void addLast0(AbstractChannelHandlerContext newCtx) {
        AbstractChannelHandlerContext prev = tail.prev;
        newCtx.prev = prev;
        newCtx.next = tail;
        prev.next = newCtx;
        tail.prev = newCtx;
    }
    ```
* 还有一点就是上面addLast方法中newContext方法的入参filterName(name, handler),这里会生成handler名字并校验是否重复(有兴趣可查看源码类DefaultChannelPipeline中  generateName(ChannelHandler handler))

2、 那么inbond和outbond是决定pipleline的数据流向的关键。
记得我们上面的newContext方法中创建的DefaultChannelHandlerContext里的构造器,有isInbound和isOutbound俩方法分别根据接口来判断Inbound和Outbound
```java
 DefaultChannelHandlerContext(
            DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
        super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
        if (handler == null) {
            throw new NullPointerException("handler");
        }
        this.handler = handler;
    }

一个Inbound事件通常由Inbound handler来处理。一个Inbound handler通常处理在IO线程产生的Inbound数据。Inbound数据通过真实的输入操作如 SocketChannel#read(ByteBuffer)来获取。如果一个inbound事件越过了最上面的inbound handler,该事件将会被抛弃到而不会通知你
一个outbound事件由outbound handler来处理。一个outbound handler通常由outbound流量如写请求产生或者转变的。如果一个outbound事件越过了底部的outbound handler,它将由channel关联的IO线程处理。IO线程通常运行的是真实的输出操作如 SocketChannel#write(byteBuffer).

 inbound 事件传播方法:
ChannelHandlerContext#fireChannelRegistered()
 ChannelHandlerContext#fireChannelActive()
  ChannelHandlerContext#fireChannelRead(Object) 
  ChannelHandlerContext#fireChannelReadComplete() 
  ChannelHandlerContext#fireExceptionCaught(Throwable) 
  ChannelHandlerContext#fireUserEventTriggered(Object) 
  ChannelHandlerContext#fireChannelWritabilityChanged() 
  ChannelHandlerContext#fireChannelInactive() 
  ChannelHandlerContext#fireChannelUnregistered()
outbound事件传播方法:
ChannelHandlerContext#bind(SocketAddress, ChannelPromise) 
ChannelHandlerContext#connect(SocketAddress, SocketAddress, ChannelPromise) 
ChannelHandlerContext#write(Object, ChannelPromise) 
ChannelHandlerContext#flush() 
ChannelHandlerContext#read() 
ChannelHandlerContext#disconnect(ChannelPromise) 
ChannelHandlerContext#close(ChannelPromise) 
ChannelHandlerContext#deregister(ChannelPromise)

如果我们捕获了一个事件, 并且想让这个事件继续传递下去, 那么需要调用 Context 相应的传播方法.
例如:

public class MyInboundHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("Connected!");
        ctx.fireChannelActive();
    }
}

public clas MyOutboundHandler extends ChannelOutboundHandlerAdapter {
    @Override
    public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
        System.out.println("Closing ..");
        ctx.close(promise);
    }
}

上面的例子中, MyInboundHandler 收到了一个 channelActive 事件, 它在处理后, 如果希望将事件继续传播下去, 那么需要接着调用 ctx.fireChannelActive().

Outbound 操作(outbound operations of a channel)
以connect为例

Bootstrap.connect -> Bootstrap.doResolveAndConnect -> Bootstrap.doResolveAndConnect0 ->Bootstrap.doConnect ->AbstractChannel.connect->pipeline.connect->tail.connect-> AbstractChannelHandlerContext.connect
最后我们以AbstractChannelHandlerContext.connect 的源码分析:

@Override
    public ChannelFuture connect(
            final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {

        if (remoteAddress == null) {
            throw new NullPointerException("remoteAddress");
        }
        if (isNotValidPromise(promise, false)) {
            // cancelled
            return promise;
        }
// DefaultChannelPipeline 内的双向链表的 tail 开始, 不断向前寻找第一个 outbound 为 true 的 AbstractChannelHandlerContext, 然后调用它的 invokeConnect
        final AbstractChannelHandlerContext next = findContextOutbound();
        //next其实是找到headContext  unsafe.connect(remoteAddress, localAddress, promise);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
当我们找到了一个 outbound 的 Context 后, 就调用它的 invokeConnect 方法, 
            next.invokeConnect(remoteAddress, localAddress, promise);
        } else {
            safeExecute(executor, new Runnable() {
                @Override
                public void run() {
                    next.invokeConnect(remoteAddress, localAddress, promise);
                }
            }, promise, null);
        }
        return promise;
    }

invokeConnect这个方法中会调用 Context 所关联着的 ChannelHandler 的 connect 方法。下面的handler()方法会返回一个handlerContex(根据上面next方法我们知道这里返回的为tailContext,但是tailContext并没有实现connect方法,所以这里的connect为其父类AbstractChannelHandlerContext的connect方法。也就是说再次从上面哪个方法开始,知道执行到headContext时,它实现了connect方法如下 方法三)。然后调用connect,包装handler所以即为hanler的connect。

private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
        if (invokeHandler()) {
            try {
                //pipeline.channel().unsafe();
                ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
            } catch (Throwable t) {
                notifyOutboundHandlerException(t, promise);
            }
        } else {
            connect(remoteAddress, localAddress, promise);
        }
    }

方法三 headContext类找的connect方法

@Override
public void connect(
ChannelHandlerContext ctx,
SocketAddress remoteAddress, SocketAddress localAddress,
ChannelPromise promise) throws Exception {
unsafe.connect(remoteAddress, localAddress, promise);
}
所以最终是unsafe.connect,而这unsafe的由来我们前面也分析过,看HeadContext的构造器unsafe = pipeline.channel().unsafe(); 所以它是来自channel。那么channel来自哪呢

  HeadContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, HEAD_NAME, true, true);
            unsafe = pipeline.channel().unsafe();
            setAddComplete();
        }

还记得 .channel(NioSocketChannel.class)这里就是channel的来源
接下来我们找到unsafe是哪里创建的,查看NioSocketChannel构造器

 public NioSocketChannel(Channel parent, SocketChannel socket) {
        super(parent, socket);
        config = new NioSocketChannelConfig(this, socket.socket());
    }

构造器跟踪流程:NioSocketChannel->AbstractNioByteChannel->AbstractChannel

 protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }

同时newUnsafe() 并没有再AbstractChannel实现,而是在NioSocketChannel实现,这是为什么呢?

 protected AbstractNioUnsafe newUnsafe() {
        return new NioSocketChannelUnsafe();
    }

其实在子类实现,是由于不同的协议用的Unsafe会不同,所以要根据子类区别对待
继续跟踪NioSocketChannelUnsafe但是该类并未实现connect方法,所以查找父类直到找到
AbstractNioUnsafe中的connect方法

 @Override
        public final void connect(
                final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
           //省略
                //doConnect这里的实现在子类中
                if (doConnect(remoteAddress, localAddress)) {
                    fulfillConnectPromise(promise, wasActive);
                } else {
                    connectPromise = promise;
                    requestedRemoteAddress = remoteAddress;

                    // Schedule connect timeout.
                    int connectTimeoutMillis = config().getConnectTimeoutMillis();
                    if (connectTimeoutMillis > 0) {
                        connectTimeoutFuture = eventLoop().schedule(new Runnable() {
                            @Override
                            public void run() {
                                ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
                                ConnectTimeoutException cause =
                                        new ConnectTimeoutException("connection timed out: " + remoteAddress);
                                if (connectPromise != null && connectPromise.tryFailure(cause)) {
                                    close(voidPromise());
                                }
                            }
                        }, connectTimeoutMillis, TimeUnit.MILLISECONDS);
                    }
                   //省略
        }

这段代码的重点就在doConnect,而这个方法在该类中是没有实现的,实现类在子类NioSocketChannel中

 @Override
    protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
        if (localAddress != null) {
            doBind0(localAddress);
        }

        boolean success = false;
        try {
            boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
            if (!connected) {
                selectionKey().interestOps(SelectionKey.OP_CONNECT);
            }
            success = true;
            return connected;
        } finally {
            if (!success) {
                doClose();
            }
        }
    }

注意:上面这个方法就是和java的NIO联系的地方了
重点分析:
1、首先doBind0方法 使用SocketUtils.bind(javaChannel(), localAddress);
其中的javaChannel()根据java版本选择nio还是bio

private void doBind0(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) {
            SocketUtils.bind(javaChannel(), localAddress);
        } else {
            SocketUtils.bind(javaChannel().socket(), localAddress);
        }
    }

在NIO中javaChannel其实获取SelectableChannel,这里SelectableChannel在之前介绍过。所以bind方法
最后调用socketChannel.bind(address); nio的在前面已介绍这里不再赘述。至此connect方法就追踪到这里。
总结connect事件在outbound中的顺序,结合上面Bootstrap.connect最后到达handler的connect就形成了下面这个循环

Context.connect -> Connect.findContextOutbound -> next.invokeConnect -> handler.connect -> Context.connect
直到head中的connect,这里我们上面分析过了。所以从connect事件来管中窥豹的话。就借用官网的数据流程图吧
参考官网的事件流转图

                                             I/O Request
                                        via Channel or
                                    ChannelHandlerContext
                                                  |
+---------------------------------------------------+---------------+
|                           ChannelPipeline         |               |
|                                                  \|/              |
|    +---------------------+            +-----------+----------+    |
|    | Inbound Handler  N  |            | Outbound Handler  1  |    |
|    +----------+----------+            +-----------+----------+    |
|              /|\                                  |               |
|               |                                  \|/              |
|    +----------+----------+            +-----------+----------+    |
|    | Inbound Handler N-1 |            | Outbound Handler  2  |    |
|    +----------+----------+            +-----------+----------+    |
|              /|\                                  .               |
|               .                                   .               |
| ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
|        [ method call]                       [method call]         |
|               .                                   .               |
|               .                                  \|/              |
|    +----------+----------+            +-----------+----------+    |
|    | Inbound Handler  2  |            | Outbound Handler M-1 |    |
|    +----------+----------+            +-----------+----------+    |
|              /|\                                  |               |
|               |                                  \|/              |
|    +----------+----------+            +-----------+----------+    |
|    | Inbound Handler  1  |            | Outbound Handler  M  |    |
|    +----------+----------+            +-----------+----------+    |
|              /|\                                  |               |
+---------------+-----------------------------------+---------------+
              |                                  \|/
+---------------+-----------------------------------+---------------+
|               |                                   |               |
|       [ Socket.read() ]                    [ Socket.write() ]     |
|                                                                   |
|  Netty Internal I/O Threads (Transport Implementation)            |
+-------------------------------------------------------------------+
  • 由于篇幅过长下篇继续讲解Inbound事件的源码

相关文章

网友评论

    本文标题:第十九节 netty源码分析之 pipleline和handl

    本文链接:https://www.haomeiwen.com/subject/hddljqtx.html