Netty服务端创建分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/shenchaohao12321/article/details/88925505

1、Netty服务端创建源码分析

当我们直接使用 JDK NIO的类库开发基于NIO的异步服务端时,需要使用到多路复用器 Selector、 ServerSocketChannel、 SocketChannel、 ByteBuffer、 SelectionKey等,相比于传统的BIO开发,NIO的开发要复杂很多,开发出稳定、高性能的异步通信框架,一直是个难题,Netty为了向使用者屏蔽NlO通信的底层细节,在和用户交互的边界做了封装,目的就是为了减少用户开发工作量,降低开发难度。 ServerBootstrap是 Socket服务端的启动辅助类,用户通过 ServerBootstrap可以方便地创建Netty的服务端。

1.1、Netty服务端创建时序图

步骤1:创建 ServerBootstrap实例。 ServerBootstrap是 Netty服务端的启动辅助类,它提供了一系列的方法用于设置服务端启动相关的参数。底层通过门面模式对各种能力进行抽象和封装,尽量不需要用户跟过多的底层API打交道,以降低用户的开发难度。我们在创建 ServerBootstrap实例时,会惊讶地发现 ServerBootstrap只有一个无参的构造函数,作为启动辅助类这让人不可思议,因为它需要与多个其他组件或者类交互。ServerBootstrap构造函数没有参数的根本原因是因为它的参数太多了,而且未来也可能会发生变化,为了解决这个问题,就需要引入 Builder模式。《 Effective java》第二版第2条建议遇到多个构造器参数时要考虑用构建器,关于多个参数构造函数的缺点和使用构建器的优点大家可以查阅《 Effective java》,在此不再详述。
步骤2:设置并绑定 Reactor线程池。Netty的 Reactor线程池是 EventLoopGroup,它实际就是 EventLoop的数组。 EventLoop的职责是处理所有注册到本线程多路复用器Selector上的 Channel, Selector的轮询操作由绑定的 EventLoop线程run方法驱动,在一个循环体内循环执行。值得说明的是, EventLoop的职责不仅仅是处理网络IO事件,用户自定义的Task和定时任务Task也统一由 EventLoop负责处理,这样线程模型就实现了统一。从调度层面看,也不存在从 EventLoop线程中再启动其他类型的线程用于异步执行另外的任务,这样就避免了多线程并发操作和锁竞争,提升了IO线程的处理和调度性能。
步骤3:设置并绑定服务端 Channel作为NIO服务端,需要创建 ServerSocketChannel,Netty对原生的NIO类库进行了封装,对应实现是 NioServerSocketChannel对于用户而言,不需要关心服务端 Channel的底层实现细节和工作原理,只需要指定具体使用哪种服务端
Channel即可。因此,Netty的 ServerBootstrap方法提供了 channel方法用于指定服务端Channel的类型。Netty通过工厂类,利用反射创建 NioServerSocketChannel对象。由于服务端监听端口往往只需要在系统启动时才会调用,因此反射对性能的影响并不大。相关代码如下。

public ServerBootstrap channel(Class<? extends ServerChannel> channelClass) {
    if (channelClass == null) {
        throw new NullPointerException("channelClass");
    }
    return channelFactory(new ServerBootstrapChannelFactory<ServerChannel>(channelClass));
}

步骤4:链路建立的时候创建并初始化 ChannelPipeline。 ChannelPipeline并不是NlO服务端必需的,它本质就是一个负责处理网络事件的职责链,负责管理和执行ChannelHandler。网络事件以事件流的形式在 ChannelPipeline中流转,由 ChannelPipeline根据 ChannelHandler的执行策略调度 ChannelHandler的执行。典型的网络事件如下
(1)链路注册;
(2)链路激活
(3)链路断开;
(4)接收到请求消息;
(5)请求消息接收并处理完毕;
(6)发送应答消息
(7)链路发生异常;
(8)发生用户自定义事件。

步骤5:初始化 ChannelPipeline完成之后,添加并设置 ChannelHandler。ChannelHandler是Netty提供给用户定制和扩展的关键接口。利用 ChannelHandler用户可以完成大多数的功能定制,例如消息编解码、心跳、安全认证、 TSLISSL认证、流量控制和流量整形等。Netty同时也提供了大量的系统 ChannelHandler供用户使用,比较实用的系统Channelhandler总结如下。
(1)系统编解码框架 ByteToMessageCodec;
(2)通用基于长度的半包解码器—— LengthFieldBasedFrameDecoder;
(3)码流日志打印 Handler—— Logging Handler
(4)SSL安全认证 Handler—— SsIHandler;
(5)链路空闲检测 Handler—— IdleStateHandler;
(6)流量整形 Handler—— ChannelTrafficShapingHandler;
(7)Base64编解码——Base64Decoder和Base64Encoder。
创建和添加 ChannelHandler的代码示例如下。

.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch)
            throws Exception {
        ch.pipeline().addLast(new EchoServerHandler());
    }
});

步骤6:绑定并启动监听端口。在绑定监听端口之前系统会做一系列的初始化和检测工作,完成之后,会启动监听端口,并将 ServerSocketChannel注册到 Selector上监听客户端连接,相关代码如下。

@Override
protected void doBind(SocketAddress localAddress) throws Exception {
    javaChannel().socket().bind(localAddress, config.getBacklog());
}

步骤7: Selector轮询。由 Reactor线程 NioEventLoop负责调度和执行 Selector轮询操作,选择准备就绪的 Channel集合,相关代码如下。

private void select() throws IOException {
    Selector selector = this.selector;
    try {
        int selectCnt = 0;
        long currentTimeNanos = System.nanoTime();
        long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
        for (;;) {
            long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
            if (timeoutMillis <= 0) {
                if (selectCnt == 0) {
                    selector.selectNow();
                    selectCnt = 1;
                }
                break;
            }

            int selectedKeys = selector.select(timeoutMillis);
            selectCnt ++;

            if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks()) {
                // Selected something,
                // waken up by user, or
                // the task queue has a pending task.
                break;
            }

            if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
                    selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
                // The selector returned prematurely many times in a row.
                // Rebuild the selector to work around the problem.
                logger.warn(
                        "Selector.select() returned prematurely {} times in a row; rebuilding selector.",
                        selectCnt);

                rebuildSelector();
                selector = this.selector;

                // Select again to populate selectedKeys.
                selector.selectNow();
                selectCnt = 1;
                break;
            }

            currentTimeNanos = System.nanoTime();
        }

        if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
            if (logger.isDebugEnabled()) {
                logger.debug("Selector.select() returned prematurely {} times in a row.", selectCnt - 1);
            }
        }
    } catch (CancelledKeyException e) {
        if (logger.isDebugEnabled()) {
            logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector - JDK bug?", e);
        }
        // Harmless exception - log anyway
    }
}

步骤8:当轮询到准备就绪的 Channel之后,就由 Reactor线程 NioEventLoop执行ChannelPipeline的相应方法,最终调度并执行 ChannelHandler,接口如图所示。

步骤9:执行 Netty系统 Channelhandler和用户添加定制的 Channelhandler。ChannelPipeline根据网络事件的类型,调度并执行 Channelhandler,相关代码如下。

@Override
public ChannelHandlerContext fireChannelRead(Object msg) {
    DefaultChannelHandlerContext next = findContextInbound(MASK_CHANNEL_READ);
    next.invoker.invokeChannelRead(next, msg);
    return this;
}

1.2、Netty服务端创建源码分析

首先通过构造函数创建 ServerBootstrap实例,随后,通常会创建两个EventLoopGroup(并不是必须要创建两个不同的 EventLoopGroup,也可以只创建一个并共享),代码如下
EventLoopGroup acceptorGroup = new NioEventLoopGroup();
EventLoopGroup IOGroup = new NioEventLoopGroup();
NioEventLoopGroup实际就是Reactor线程池,负责调度和执行客户端的接入、网络读写事件的处理、用户自定义任务和定时任务的执行。通过 ServerBootstrap的 group方法将两个 EventLoopGroup实例传入,代码如下。

public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
    super.group(parentGroup);
    if (childGroup == null) {
        throw new NullPointerException("childGroup");
    }
    if (this.childGroup != null) {
        throw new IllegalStateException("childGroup set already");
    }
    this.childGroup = childGroup;
    return this;
}

其中父 NioEventLoopGroup被传入了父类构造函数中,代码如下。

public B group(EventLoopGroup group) {
    if (group == null) {
        throw new NullPointerException("group");
    }
    if (this.group != null) {
        throw new IllegalStateException("group set already");
    }
    this.group = group;
    return (B) this;
}

该方法会被客户端和服务端重用,用于设置工作I/O线程,执行和调度网络事件的读写线程组和线程类型设置完成后,需要设置服务端 Channel用于端口监听和客户端链路接入。Netty通过 Channel工厂类来创建不同类型的 Channel,对于服务端,需要创建NioServerSocketChannel。所以,通过指定 Channel类型的方式创建 Channel工厂。
ServerBootstrapChannelFactory是 ServerBootstrap的内部静态类,职责是根据 Channel的类型通过反射创建 Channel的实例,服务端需要创建的是 NioServerSocketChannel实例,代码如下。

public T newChannel(EventLoop eventLoop, EventLoopGroup childGroup) {
    try {
        Constructor<? extends T> constructor = clazz.getConstructor(EventLoop.class, EventLoopGroup.class);
        return constructor.newInstance(eventLoop, childGroup);
    } catch (Throwable t) {
        throw new ChannelException("Unable to create Channel from class " + clazz, t);
    }
}

指定 NioServerSocketChannel后,需要设置TCP的一些参数,作为服务端,主要是要设置TCP的 backlog参数,底层C的对应接口定义如下。

int listen(int fd, int backlog);

backlog指定了内核为此套接口排队的最大连接个数,对于给定的监听套接口,内核要维护两个队列:未链接队列和已连接队列,根据TCP三路握手过程中三个分节来分隔这两个队列。服务器处于 listen状态时,收到客户端syn分节( connect)时在未完成队列中创建一个新的条目,然后用三路握手的第二个分节即服务器的syn响应客户端,此条目在第三个分节到达前(客户端对服务器syn的ack)一直保留在未完成连接队列中,如果三路握手完成,该条目将从未完成连接队列搬到已完成连接队列尾部。当进程调用 accept时,从已完成队列中的头部取出一个条目给进程,当已完成队列为空时进程将睡眠,直到有条目在已完成连接队列中才唤醒。 backlog被规定为两个队列总和的最大值,大多数实现默认值为5,但在高并发web服务器中此值显然不够,Lighttpd中此值达到128×8。需要设置此值更大一些的原因是未完成连接队列的长度可能因为客户端syn的到达及等待三路握手第三个分节的到达延时而增大。Nety默认的 backlog为100,当然,用户可以修改默认值,这需要根据实际场景和网络状况进行灵活设置。
TCP参数设置完成后,用户可以为启动辅助类和其父类分别指定 Handler两类 Handler的用途不同:子类中的 Handler是 NioServerSocketchannel对应的 ChannelPipeline的Handler;父类中的 Handler是客户端新接入的连接 Socketchannel对应的 ChannelPipeline的 Handler。两者的区别可以通过图13-3来展示。

本质区别就是: ServerBootstrap中的 Handler是 NioServerSocketchannel使用的,所有连接该监听端口的客户端都会执行它;父类 AbstractBootstrap中的 Handler是个工厂类,它为每个新接入的客户端都创建一个新的 Handler服务端启动的最后一步,就是绑定本地端口,启动服务,下面我们来分析下这部分代码。

private ChannelFuture doBind(final SocketAddress localAddress) {
    final ChannelFuture regFuture = initAndRegister();//NO.1
    final Channel channel = regFuture.channel();
    if (regFuture.cause() != null) {
        return regFuture;
    }

    final ChannelPromise promise;
    if (regFuture.isDone()) {//NO.2
        promise = channel.newPromise();
        doBind0(regFuture, channel, localAddress, promise);
    } else {
        // Registration future is almost always fulfilled already, but just in case it's not.
        promise = new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE);
        regFuture.addListener(new ChannelFutureListener() {
            @Override//NO.3
            public void operationComplete(ChannelFuture future) throws Exception {
                doBind0(regFuture, channel, localAddress, promise);
            }
        });
    }

    return promise;
}

先看下NO.1。首先创建 Channel, createChannel由子类 ServerBootstrap实现,创建新的 NioServerSocketChannel。它有两个参数:参数1是从父类的NO线程池中顺序获取个 NioEventLoop,它就是服务端用于监听和接收客户端连接的 Reactor线程;参数2是所谓的 worker Group线程池,它就是处理I/O读写的 Reactor线程组,相关代码如下。

final ChannelFuture initAndRegister() {
    Channel channel;
    try {
        channel = createChannel();
    } catch (Throwable t) {
        return VoidChannel.INSTANCE.newFailedFuture(t);
    }

    try {
        init(channel);
    } catch (Throwable t) {
        channel.unsafe().closeForcibly();
        return channel.newFailedFuture(t);
    }

    ChannelPromise regFuture = channel.newPromise();
    channel.unsafe().register(regFuture);
    if (regFuture.cause() != null) {
        if (channel.isRegistered()) {
            channel.close();
        } else {
            channel.unsafe().closeForcibly();
        }
    }

    // If we are here and the promise is not failed, it's one of the following cases:
    // 1) If we attempted registration from the event loop, the registration has been completed at this point.
    //    i.e. It's safe to attempt bind() or connect() now beause the channel has been registered.
    // 2) If we attempted registration from the other thread, the registration request has been successfully
    //    added to the event loop's task queue for later execution.
    //    i.e. It's safe to attempt bind() or connect() now:
    //         because bind() or connect() will be executed *after* the scheduled registration task is executed
    //         because register(), bind(), and connect() are all bound to the same thread.

    return regFuture;
}

NioServerSocketchannel创建成功后,对它进行初始化,初始化工作主要有以下三点。

@Override
void init(Channel channel) throws Exception {
    //(1)设置 Socket参数和 NioServerSocketChannel的附加属性,代码如下。
    final Map<ChannelOption<?>, Object> options = options();
    synchronized (options) {
        channel.config().setOptions(options);
    }

    final Map<AttributeKey<?>, Object> attrs = attrs();
    synchronized (attrs) {
        for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
            @SuppressWarnings("unchecked")
            AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
            channel.attr(key).set(e.getValue());
        }
    }
    //(2)将AbstractBootstrap的Handler添加到NioServerSocketChannel的ChannelPipeline中,代码如下。
    ChannelPipeline p = channel.pipeline();
    if (handler() != null) {
        p.addLast(handler());
    }

    final ChannelHandler currentChildHandler = childHandler;
    final Entry<ChannelOption<?>, Object>[] currentChildOptions;
    final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
    synchronized (childOptions) {
        currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
    }
    synchronized (childAttrs) {
        currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
    }
    //(3)将用于服务端注册的HandlerServerBootstrapAcceptor添加到ChannelPipeline中,代码如下。
    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(Channel ch) throws Exception {
            ch.pipeline().addLast(new ServerBootstrapAcceptor(currentChildHandler, currentChildOptions,
                    currentChildAttrs));
        }
    });
}

到此,Netty服务端监听的相关资源已经初始化完毕,就剩下最后一步一一注册NioServerSocketChannel到Reactor线程的多路复用器上,然后轮询客户端连接事件。在分析注册代码之前,我们先通过图13-4看看目前NioServerSocketchannel的ChannelPipeline的组成。

最后,我们看下 NioServer SocketChannel的注册。当 NioServerSocketChannel初始化完成之后,需要将它注册到 Reactor线程的多路复用器上监听新客户端的接入,代码如下。

@Override
public final void register(final ChannelPromise promise) {
    if (eventLoop.inEventLoop()) {
        register0(promise);
    } else {
        try {
            eventLoop.execute(new Runnable() {
                @Override
                public void run() {
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            logger.warn(
                    "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                    AbstractChannel.this, t);
            closeForcibly();
            closeFuture.setClosed();
            promise.setFailure(t);
        }
    }
}

首先判断是否是 Nio EventLoop自身发起的操作。如果是,则不存在并发操作,直接执行 Channel注册:如果由其他线程发起,则封装成一个Task放入消息队列中异步执行。此处,由于是由 Server Bootstrap所在线程执行的注册操作,所以会将其封装成Task投递到
NioEventLoop中执行,代码如下。

private void register0(ChannelPromise promise) {
    try {
        // check if the channel is still open as it could be closed in the mean time when the register
        // call was outside of the eventLoop
        if (!ensureOpen(promise)) {
            return;
        }
        doRegister();
        registered = true;
        promise.setSuccess();
        pipeline.fireChannelRegistered();
        if (isActive()) {
            pipeline.fireChannelActive();
        }
    } catch (Throwable t) {
        // Close the channel directly to avoid FD leak.
        closeForcibly();
        closeFuture.setClosed();
        if (!promise.tryFailure(t)) {
            logger.warn(
                    "Tried to fail the registration promise, but it is complete already. " +
                            "Swallowing the cause of the registration failure:", t);
        }
    }
}

将 NioServerSocketchannel注册到 NioEventLoop的 Selector上,代码如下:

@Override
protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            selectionKey = javaChannel().register(eventLoop().selector, 0, this);
            return;
        } catch (CancelledKeyException e) {
            if (!selected) {
                // Force the Selector to select now as the "canceled" SelectionKey may still be
                // cached and not removed because no Select.select(..) operation was called yet.
                eventLoop().selectNow();
                selected = true;
            } else {
                // We forced a select operation on the selector before but the SelectionKey is still cached
                // for whatever reason. JDK bug ?
                throw e;
            }
        }
    }
}

大家可能会很诧异,应该注册 OP_ACCEPT(16)到多路复用器上,怎么注册0呢?
0表示只注册,不监听任何网络操作。这样做的原因如下。
(1)注册方法是多态的,它既可以被 NioServerSocketchannel用来监听客户端的连接接入,也可以注册 Socketchannel用来监听网络读或者写操作;
(2)通过 SelectionKey的 interestOps(int ops)方法可以方便地修改监听操作位。所以,此处注册需要获取 SelectionKey并给 AbstractNioChannel的成员变量 selectionKey赋值。注册成功之后,触发 ChannelRegistered事件,方法如下

promise.setSuccess();
pipeline.fireChannelRegistered();

当 ChannelRegistered事件传递到 TailHandler后结束, TailHandler也不关心ChannelRegistered事件,因此是空实现,代码如下

@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception { }

ChannelRegistered事件传递完成后,判断 Server Socketchannel监听是否成功,如果成功,需要出发 NioServerSocketChannel的 ChannelActive事件,代码如下

if (inActive()){
    pipeline. fireChannelActive();
}

inActive也是个多态方法。如果是服务端,判断监听是否启动;如果是客户端,判断TCP连接是否完成。 ChannelActive事件在 ChannelPipeline中传递,完成之后根据配置决定是否自动触发 Channel的读操作,代码如下。

@Override
public ChannelPipeline fireChannelActive() {
    head.fireChannelActive();

    if (channel.config().isAutoRead()) {
        channel.read();
    }

    return this;
}

Abstractchannel的读操作触发 ChannelPipeline的读操作,最终调用到 HeadHandler的读方法,代码如下。

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

继续看 AbstractUnsafe的 beginRead方法,代码如下。

@Override
public void beginRead() {
    if (!isActive()) {
        return;
    }

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

由于不同类型的 Channel对读操作的准备工作不同,因此, beginRead也是个多态方法,对于NIO通信,无论是客户端还是服务端,都是要修改网络监听操作位为自身感兴趣的,对于 NioServer SocketChannel感兴趣的操作是 OP_ACCEPT(16),于是重新修改注册的操作位为 OP_ACCEPT,代码如下。

@Override
protected void doBeginRead() throws Exception {
    if (inputShutdown) {
        return;
    }

    final SelectionKey selectionKey = this.selectionKey;
    if (!selectionKey.isValid()) {
        return;
    }

    final int interestOps = selectionKey.interestOps();
    if ((interestOps & readInterestOp) == 0) {
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}

在某些场景下,当前监听的操作类型和 Chanel关心的网络事件是一致的,不需要重复注册,所以增加了&操作的判断,只有两者不一致,才需要重新注册操作位。
JDK SelectionKey有4种操作类型,分别为:
(1) OP_READ=1<<0;
(2) OP_WRITE=1<<2;
(3) OP_CONNECT=1<<3:
(4) OP_ACCEPT=1<<4。
由于只有4种网络操作类型,所以用4bit就可以表示所有的网络操作位,由于Java语言没有bit类型,所以使用了整型来表示,每个操作位代表一种网络操作类型,分别为:0001、0010、0100、1000,这样做的好处是可以非常方便地通过位操作来进行网络操作位的状态判断和状态修改,从而提升操作性能。
由于创建 NioServer SocketChannel将 readInterestOp设置成了 OP_ACCEPT,所以,在服务端链路注册成功之后重新将操作位设置为监听客户端的网络连接操作,初始化NioServerSocketchannel的代码如下。

public NioServerSocketChannel(EventLoop eventLoop, EventLoopGroup childGroup) {
    super(null, eventLoop, childGroup, newSocket(), SelectionKey.OP_ACCEPT);
    config = new DefaultServerSocketChannelConfig(this, javaChannel().socket());
}

2、客户端接入源码分析

负责处理网络读写、连接和客户端请求接入的 Reactor线程就是 NioEventLoop,下面我们分析下 NioEventLoop是如何处理新的客户端连接接入的。当多路复用器检测到新的准备就绪的 Channel时,默认执行 processSelectedKeysOptimized方法,代码如下

if (selectedKeys != null) {
    processSelectedKeysOptimized(selectedKeys.flip());
} else {
    processSelectedKeysPlain(selector.selectedKeys());
}

由于 Channel的 Attachment是 NioServerSocketchannel,所以执行 processSelectedKey方法,根据就绪的操作位,执行不同的操作。此处,由于监听的是连接操作,所以执行unsafe.read()方法。由于不同的 Channel执行不同的操作,所以 NioUnsafe被设计成接口,
由不同的 Channel内部的 NioUnsafe实现类负责具体实现。我们发现 reado方法的实现有两个,分别是 NioByteUnsafe和 NioMessageUnsafe。对于 NioServerSocketChannel,它使用的是 NioMessageUnsafe,它的read方法代码如下

@Override
public void read() {
    assert eventLoop().inEventLoop();
    if (!config().isAutoRead()) {
        removeReadOp();
    }

    final ChannelConfig config = config();
    final int maxMessagesPerRead = config.getMaxMessagesPerRead();
    final boolean autoRead = config.isAutoRead();
    final ChannelPipeline pipeline = pipeline();
    boolean closed = false;
    Throwable exception = null;
    try {
        for (;;) {
            int localRead = doReadMessages(readBuf);
            if (localRead == 0) {
                break;
            }
            if (localRead < 0) {
                closed = true;
                break;
            }

            if (readBuf.size() >= maxMessagesPerRead | !autoRead) {
                break;
            }
        }
    } catch (Throwable t) {
        exception = t;
    }

    int size = readBuf.size();
    for (int i = 0; i < size; i ++) {
        pipeline.fireChannelRead(readBuf.get(i));
    }
    readBuf.clear();
    pipeline.fireChannelReadComplete();

    if (exception != null) {
        if (exception instanceof IOException) {
            // ServerChannel should not be closed even on IOException because it can often continue
            // accepting incoming connections. (e.g. too many open files)
            closed = !(AbstractNioMessageChannel.this instanceof ServerChannel);
        }

        pipeline.fireExceptionCaught(exception);
    }

    if (closed) {
        if (isOpen()) {
            close(voidPromise());
        }
    }
}

对 doReadMessages方法进行分析,发现它实际就是接收新的客户端连接并创建NioSocketChannel,代码如下。

@Override
protected int doReadMessages(List<Object> buf) throws Exception {
    SocketChannel ch = javaChannel().accept();

    try {
        if (ch != null) {
            buf.add(new NioSocketChannel(this, childEventLoopGroup().next(), ch));
            return 1;
        }
    } catch (Throwable t) {
        logger.warn("Failed to create a new channel from an accepted socket.", t);

        try {
            ch.close();
        } catch (Throwable t2) {
            logger.warn("Failed to close a socket.", t2);
        }
    }

    return 0;
}

接收到新的客户端连接后,触发 ChannelPipeline的 ChannelRead方法,代码如下。

int size = readBuf.size();
for (int i = 0; i < size; i ++) {
    pipeline.fireChannelRead(readBuf.get(i));
}

执行 headChannelHandlerContext的 fireChannelRead方法,事件在 ChannelPipeline中传递,执行 ServerBootstrapAcceptor的 channelread方法,代码如下。

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    Channel child = (Channel) msg;

    child.pipeline().addLast(childHandler);

    for (Entry<ChannelOption<?>, Object> e: childOptions) {
        try {
            if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {
                logger.warn("Unknown channel option: " + e);
            }
        } catch (Throwable t) {
            logger.warn("Failed to set a channel option: " + child, t);
        }
    }

    for (Entry<AttributeKey<?>, Object> e: childAttrs) {
        child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
    }

    child.unsafe().register(child.newPromise());
}

该方法主要分为如下三个步骤。
第一步:将启动时传入的 childhandler加入到客户端 SocketChannel的ChannelPipeline中。
第二步:设置客户端 Socketchannel的TCP参数
第三步:注册 Socketchannel到多路复用器。
以上三个步骤执行完成之后,下面我们展开看下 NioSocketChannel的 register方法,代码如下所示。

@Override
public final void register(final ChannelPromise promise) {
    if (eventLoop.inEventLoop()) {
        register0(promise);
    } else {
        try {
            eventLoop.execute(new Runnable() {
                @Override
                public void run() {
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            logger.warn(
                    "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                    AbstractChannel.this, t);
            closeForcibly();
            closeFuture.setClosed();
            promise.setFailure(t);
        }
    }
}

private void register0(ChannelPromise promise) {
    try {
        // check if the channel is still open as it could be closed in the mean time when the register
        // call was outside of the eventLoop
        if (!ensureOpen(promise)) {
            return;
        }
        doRegister();
        registered = true;
        promise.setSuccess();
        pipeline.fireChannelRegistered();
        if (isActive()) {
            pipeline.fireChannelActive();
        }
    } catch (Throwable t) {
        // Close the channel directly to avoid FD leak.
        closeForcibly();
        closeFuture.setClosed();
        if (!promise.tryFailure(t)) {
            logger.warn(
                    "Tried to fail the registration promise, but it is complete already. " +
                            "Swallowing the cause of the registration failure:", t);
        }
    }
}

NioSocketchannel的注册方法与 ServerSocketChannel的一致,也是将 Channel注册到Reactor线程的多路复用器上。由于注册的操作位是0,所以,此时 NioSocketchannel还不能读取客户端发送的消息,那什么时候修改监听操作位为 OP_READ呢,别着急,继续看代码。
执行完注册操作之后,紧接着会触发 ChannelReadComplete事件。我们继续分析ChannelReadComplete在 ChannelPipeline中的处理流程:Netty的 Header和Tail本身不关注 ChannelReadComplete事件就直接透传,执行完 ChannelReadComplete后,接着执行Pipeline的 read()方法,最终执行 HeadHandler的 read()方法。
HeadHandler read()方法的代码已经在之前的小节介绍过,用来将网络操作位修改为读操作。创建 NioSocket Channel的时候已经将 AbstractNioChannel的 readInterestOp设置为OP_READ,这样,执行 selectionKey.interestOps( interestOps | readInterestOp)操作时就会把操作位设置为 OP_READ。代码如下。

protected AbstractNioByteChannel(Channel parent, EventLoop eventLoop, SelectableChannel ch) {
    super(parent, eventLoop, ch, SelectionKey.OP_READ);
}

到此,新接入的客户端连接处理完成,可以进行网络读写等IO操作。

猜你喜欢

转载自blog.csdn.net/shenchaohao12321/article/details/88925505