Netty的基本組件之Channel源碼剖析(下)

5.2 AbstractNioChannel源碼分析

?AbstractNioChannel從名字可以看出是對NIO的抽象,首先看下這個類的NioUnsafe接口:

/**
     * Special {@link Unsafe} sub-type which allows to access the underlying {@link SelectableChannel}
     */
    public interface NioUnsafe extends Unsafe {
        /**
         * Return underlying {@link SelectableChannel}
         */
        SelectableChannel ch(); // 對應(yīng)NIO中的JDK實現(xiàn)的Channel

        /**
         * Finish connect
         */
        void finishConnect(); // 連接完成

        /**
         * Read from underlying {@link SelectableChannel}
         */
        void read(); // 從JDK的Channel中讀取數(shù)據(jù)

        void forceFlush();
    }

?回憶NIO的三大概念:Channel、Buffer、Selector,Netty的Channel包裝了JDK的Channel從而實現(xiàn)更為復(fù)雜的功能。Unsafe中可以使用ch()方法,NioChannel中可以使用javaChannel()方法獲得JDK的Channel。接口中定義了finishConnect()方法是因為SelectableChannel設(shè)置為非阻塞模式時,connect()方法會立即返回,此時連接操作可能沒有完成,如果沒有完成,則需要調(diào)用JDK的finishConnect()方法完成連接操作。也許你已經(jīng)注意到,AbstractUnsafe中并沒有connect事件框架,這是因為并不是所有連接都有標(biāo)準(zhǔn)的connect過程,比如Netty的LocalChannel和EmbeddedChannel。但是NIO中的連接操作則有較為標(biāo)準(zhǔn)的流程,在介紹Connect事件框架前,先介紹一下其中使用到的相關(guān)字段,這些字段定義在AbstractNioChannel中:

/**
     * The future of the current connection attempt.  If not null, subsequent
     * connection attempts will fail.
     */
    private ChannelPromise connectPromise; // 連接異步結(jié)果
    private ScheduledFuture<?> connectTimeoutFuture; // 連接超時檢測任務(wù)異步結(jié)果
    private SocketAddress requestedRemoteAddress; // 連接的遠(yuǎn)端地址

Connect事件框架:

@Override
        public final void connect(
                final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
            if (!promise.setUncancellable() || !ensureOpen(promise)) {
                return;   // Channel已被關(guān)閉
            }

            try {
                if (connectPromise != null) {
                    // Already a connect in process.
                    throw new ConnectionPendingException(); // 已有連接操作正在進(jìn)行
                }

                boolean wasActive = isActive();
                // 模板方法,細(xì)節(jié)子類完成
                if (doConnect(remoteAddress, localAddress)) {
                    fulfillConnectPromise(promise, wasActive); // 連接操作已完成
                } else {
                    // 連接操作尚未完成
                    connectPromise = promise;
                    requestedRemoteAddress = remoteAddress;

                    // 這部分代碼為Netty的連接超時機(jī)制
                    // 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);
                    }

                    promise.addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture future) throws Exception {
                            // 連接操作取消則連接超時檢測任務(wù)取消
                            if (future.isCancelled()) {
                                if (connectTimeoutFuture != null) {
                                    connectTimeoutFuture.cancel(false);
                                }
                                connectPromise = null;
                                close(voidPromise());
                            }
                        }
                    });
                }
            } catch (Throwable t) {
                promise.tryFailure(annotateConnectException(t, remoteAddress));
                closeIfClosed();
            }
        }

?Connect事件框架中包含了Netty的連接超時檢測機(jī)制:向EventLoop提交一個調(diào)度任務(wù),設(shè)定的超時時間已到則向連接操作的異步結(jié)果設(shè)置失敗然后關(guān)閉連接。fulfillConnectPromise()設(shè)置異步結(jié)果為成功并觸發(fā)Channel的Active事件:

private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
            if (promise == null) {
                // Closed via cancellation and the promise has been notified already.
                return; // 操作已取消或Promise已被通知
            }

            // Get the state as trySuccess() may trigger an ChannelFutureListener that will close the Channel.
            // We still need to ensure we call fireChannelActive() in this case.
            boolean active = isActive();

            // trySuccess() will return false if a user cancelled the connection attempt.
            boolean promiseSet = promise.trySuccess();

            // Regardless if the connection attempt was cancelled, channelActive() event should be triggered,
            // because what happened is what happened.
            if (!wasActive && active) {
                pipeline().fireChannelActive();
            }

            // If a user cancelled the connection attempt, close the channel, which is followed by channelInactive().
            if (!promiseSet) {
                close(voidPromise());
            }
        }

FinishConnect事件框架:

@Override
        public final void finishConnect() {
            // Note this method is invoked by the event loop only if the connection attempt was
            // neither cancelled nor timed out.

            assert eventLoop().inEventLoop();

            try {
                boolean wasActive = isActive();
                doFinishConnect();
                fulfillConnectPromise(connectPromise, wasActive); // 首次Active觸發(fā)Active事件
            } catch (Throwable t) {
                fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress));
            } finally {
                // Check for null as the connectTimeoutFuture is only created if a connectTimeoutMillis > 0 is used
                // See https://github.com/netty/netty/issues/1770
                if (connectTimeoutFuture != null) {
                    connectTimeoutFuture.cancel(false);  // 連接完成,取消超時檢測任務(wù)
                }
                connectPromise = null;
            }
        }

finishConnect()只由EventLoop處理就緒selectionKey的OP_CONNECT事件時調(diào)用,從而完成連接操作。注意:連接操作被取消或者超時不會使該方法被調(diào)用。
Flush事件細(xì)節(jié):

@Override
        protected final void flush0() {
            // Flush immediately only when there's no pending flush.
            // If there's a pending flush operation, event loop will call forceFlush() later,
            // and thus there's no need to call it now.
            if (!isFlushPending()) {
                super.flush0(); // 調(diào)用父類方法,在父類判斷是否已經(jīng)又調(diào)用;
            }
        }

        @Override
        public final void forceFlush() {
            // directly call super.flush0() to force a flush now
            super.flush0();
        }

        private boolean isFlushPending() {
            SelectionKey selectionKey = selectionKey();
            return selectionKey.isValid() && (selectionKey.interestOps() & SelectionKey.OP_WRITE) != 0;
        }

?forceFlush()方法由EventLoop處理就緒selectionKey的OP_WRITE事件時調(diào)用,將緩沖區(qū)中的數(shù)據(jù)寫入Channel。isFlushPending()方法容易導(dǎo)致困惑:為什么selectionKey關(guān)心OP_WRITE事件表示正在Flush呢?OP_WRITE表示通道可寫,而一般情況下通道都可寫,如果selectionKey一直關(guān)心OP_WRITE事件,那么將不斷從select()方法返回從而導(dǎo)致死循環(huán)。Netty使用一個寫緩沖區(qū),write操作將數(shù)據(jù)放入緩沖區(qū)中,flush時設(shè)置selectionKey關(guān)心OP_WRITE事件,完成后取消關(guān)心OP_WRITE事件。所以,如果selectionKey關(guān)心OP_WRITE事件表示此時正在Flush數(shù)據(jù)。
?AbstractNioUnsafe還有最后一個方法removeReadOp():

protected final void removeReadOp() {
            SelectionKey key = selectionKey();
            // Check first if the key is still valid as it may be canceled as part of the deregistration
            // from the EventLoop
            // See https://github.com/netty/netty/issues/2104
            if (!key.isValid()) {
                return; // selectionKey已被取消
            }
            int interestOps = key.interestOps();
            if ((interestOps & readInterestOp) != 0) {
                // only remove readInterestOp if needed
                key.interestOps(interestOps & ~readInterestOp); //設(shè)置為不再感興趣
            }
        }

?Netty中將服務(wù)端的OP_ACCEPT和客戶端的Read統(tǒng)一抽象為Read事件,在NIO底層I/O事件使用bitmap表示,一個二進(jìn)制位對應(yīng)一個I/O事件。當(dāng)一個二進(jìn)制位為1時表示關(guān)心該事件,readInterestOp的二進(jìn)制表示只有1位為1,所以體會interestOps & ~readInterestOp的含義,可知removeReadOp()的功能是設(shè)置SelectionKey不再關(guān)心Read事件。類似的,還有setReadOp()、removeWriteOp()、setWriteOp()等等。
?分析完AbstractNioUnsafe,我們再分析AbstractNioChannel,首先看其中還沒講解的字段:

private final SelectableChannel ch; // 包裝的JDK Channel
    protected final int readInterestOp; // Read事件,服務(wù)端OP_ACCEPT,其他OP_READ
    volatile SelectionKey selectionKey; // JDK Channel對應(yīng)的選擇鍵
    boolean readPending;  // 底層讀事件進(jìn)行標(biāo)記

再看一下構(gòu)造方法:

/**
     * Create a new instance
     *
     * @param parent            the parent {@link Channel} by which this instance was created. May be {@code null}
     * @param ch                the underlying {@link SelectableChannel} on which it operates
     * @param readInterestOp    the ops to set to receive data from the {@link SelectableChannel}
     */
    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            ch.configureBlocking(false); // 設(shè)置為非阻塞模式
        } catch (IOException e) {
            try {
                ch.close();
            } catch (IOException e2) {
                if (logger.isWarnEnabled()) {
                    logger.warn(
                            "Failed to close a partially initialized socket.", e2);
                }
            }

            throw new ChannelException("Failed to enter non-blocking mode.", e);
        }
    }

?其中的ch.configureBlocking(false)方法設(shè)置Channel為非阻塞模式,從而為Netty提供非阻塞處理I/O事件的能力。
?對于AbstractNioChannel的方法,我們主要分析它實現(xiàn)I/O事件框架細(xì)節(jié)部分的doXXX()方法。

    @Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 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.
                    // 選擇鍵取消重新selectNow(),清除因取消操作而緩存的選擇鍵
                    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;
                }
            }
        }
    }

    @Override
    protected void doDeregister() throws Exception {
        eventLoop().cancel(selectionKey());
    }

?對于Register事件,當(dāng)Channel屬于NIO時,已經(jīng)可以確定注冊操作的全部細(xì)節(jié):將Channel注冊到給定NioEventLoop的selector上即可。注意,其中第二個參數(shù)0表示注冊時不關(guān)心任何事件,第三個參數(shù)為Netty的NioChannel對象本身。對于Deregister事件,選擇鍵執(zhí)行cancle()操作,選擇鍵表示JDK Channel和selector的關(guān)系,調(diào)用cancle()終結(jié)這種關(guān)系,從而實現(xiàn)從NioEventLoop中Deregister。需要注意的是:cancle操作調(diào)用后,注冊關(guān)系不會立即生效,而會將cancle的key移入selector的一個取消鍵集合,當(dāng)下次調(diào)用select相關(guān)方法或一個正在進(jìn)行的select調(diào)用結(jié)束時,會從取消鍵集合中移除該選擇鍵,此時注銷才真正完成。一個Cancel的選擇鍵為無效鍵,調(diào)用它相關(guān)的方法會拋出CancelledKeyException。

@Override
    protected void doBeginRead() throws Exception {
        // Channel.read() or ChannelHandlerContext.read() was called
        final SelectionKey selectionKey = this.selectionKey;
        if (!selectionKey.isValid()) {
            return; // 選擇鍵被取消而不再有效
        }

        readPending = true; // 設(shè)置底層讀事件正在進(jìn)行

        final int interestOps = selectionKey.interestOps();
        if ((interestOps & readInterestOp) == 0) {
            // 選擇鍵關(guān)心Read事件
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }

?對于NioChannel的beginRead事件,只需將Read事件設(shè)置為選擇鍵所關(guān)心的事件,則之后的select()調(diào)用如果Channel對應(yīng)的Read事件就緒,便會觸發(fā)Netty的read()操作。

@Override
    protected void doClose() throws Exception {
        ChannelPromise promise = connectPromise;
        if (promise != null) {
            // Use tryFailure() instead of setFailure() to avoid the race against cancel().
            // 連接操作還在進(jìn)行,但用戶調(diào)用close操作
            promise.tryFailure(DO_CLOSE_CLOSED_CHANNEL_EXCEPTION);
            connectPromise = null;
        }

        ScheduledFuture<?> future = connectTimeoutFuture;
        if (future != null) { // 如果有連接超時檢測任務(wù),則取消
            future.cancel(false);
            connectTimeoutFuture = null;
        }
    }

?此處的doClose操作主要處理了連接操作相關(guān)的后續(xù)處理。并沒有實際關(guān)閉Channel,所以需要子類繼續(xù)增加細(xì)節(jié)實現(xiàn)。AbstractNioChannel中還有關(guān)于創(chuàng)建DirectBuffer的方法,將在以后必要時進(jìn)行分析。其他的方法則較為簡單,不在列出。最后提一下isCompatible()方法,說明NioChannel只在NioEventLoop中可用。

@Override
    protected boolean isCompatible(EventLoop loop) {
        return loop instanceof NioEventLoop;
    }

?AbstractNioChannel的子類實現(xiàn)分為服務(wù)端AbstractNioMessageChannel和客戶端AbstractNioByteChannel,我們將首先分析服務(wù)端AbstractNioMessageChannel。

5.3 AbstractNioMessageChannel源碼分析

?AbstractNioMessageChannel是底層數(shù)據(jù)為消息的NioChannel。在Netty中,服務(wù)端Accept的一個Channel被認(rèn)為是一條消息,UDP數(shù)據(jù)報也是一條消息。該類主要完善flush事件框架的doWrite細(xì)節(jié)和實現(xiàn)read事件框架(在內(nèi)部類NioMessageUnsafe完成)。首先看read事件框架:

@Override
        public void read() {
            assert eventLoop().inEventLoop();
            final ChannelConfig config = config();
            final ChannelPipeline pipeline = pipeline();
            final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
            allocHandle.reset(config);

            boolean closed = false;
            Throwable exception = null;
            try {
                try {
                    do {
                        int localRead = doReadMessages(readBuf); // 模板方法,讀取消息
                        if (localRead == 0) { // 沒有數(shù)據(jù)可讀
                            break;
                        }
                        if (localRead < 0) { // 讀取出錯
                            closed = true;
                            break;
                        }

                        allocHandle.incMessagesRead(localRead);
                    } while (allocHandle.continueReading());
                } catch (Throwable t) {
                    exception = t;
                }

                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    readPending = false; // 已沒有底層讀事件
                    pipeline.fireChannelRead(readBuf.get(i)); //觸發(fā)ChannelRead事件,用戶處理
                }
                readBuf.clear();
                allocHandle.readComplete();
                // ChannelReadComplete事件中如果配置autoRead則會調(diào)用beginRead,從而不斷進(jìn)行讀操作
                pipeline.fireChannelReadComplete(); // 觸發(fā)ChannelReadComplete事件,用戶處理

                if (exception != null) {
                    closed = closeOnReadError(exception);

                    pipeline.fireExceptionCaught(exception);
                }

                if (closed) {
                    inputShutdown = true;
                    if (isOpen()) {
                        close(voidPromise()); // 非serverChannel且打開則關(guān)閉
                    }
                }
            } finally {
                // Check if there is a readPending which was not processed yet.
                // This could be for two reasons:
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
                //
                // See https://github.com/netty/netty/issues/2254
                if (!readPending && !config.isAutoRead()) {
                    // 既沒有配置autoRead也沒有底層讀事件進(jìn)行
                    removeReadOp(); // 清除read事件,不再關(guān)心
                }
            }
        }
    }

?read事件框架的流程已在代碼中注明,需要注意的是讀取消息的細(xì)節(jié)doReadMessages(readBuf)方法由子類實現(xiàn)。
?我們主要分析NioServerSocketChannel,它不支持doWrite()操作,所以我們不再分析本類的flush事件框架的doWrite細(xì)節(jié)方法,直接轉(zhuǎn)向下一個目標(biāo):NioServerSocketChannel。

5.4 NioServerSocketChannel源碼分析

?你肯定已經(jīng)使用過NioServerSocketChannel,Netty的example中大量使用了此類,作為處于Channel最底層的子類,NioServerSocketChannel會實現(xiàn)I/O事件框架的底層細(xì)節(jié)。首先需要注意的是:NioServerSocketChannel只支持bind、read和close操作。

    @Override
    protected void doBind(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) { // JDK版本1.7以上
            javaChannel().bind(localAddress, config.getBacklog());
        } else {
            javaChannel().socket().bind(localAddress, config.getBacklog());
        }
    }

    @Override
    protected void doClose() throws Exception {
        javaChannel().close();
    }

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

        try {
            // 一個NioSocketChannel為一條消息
            if (ch != null) {
                buf.add(new NioSocketChannel(this, 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;
    }

?其中的實現(xiàn),都是調(diào)用JDK的Channel的方法,從而實現(xiàn)了最底層的細(xì)節(jié)。需要注意的是:此處的doReadMessages()方法每次最多返回一個消息(客戶端連接),由此可知NioServerSocketChannel的read操作一次至多處理的連接數(shù)為config.getMaxMessagesPerRead(),也就是參數(shù)值MAX_MESSAGES_PER_READ。此外doClose()覆蓋了AbstractNioChannel的實現(xiàn),因為NioServerSocketChannel不支持connect操作,所以不需要連接超時處理。
?最后,我們再看關(guān)鍵構(gòu)造方法:

/**
     * Create a new instance
     */
    public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }

    /**
     * Create a new instance using the given {@link SelectorProvider}.
     */
    public NioServerSocketChannel(SelectorProvider provider) {
        this(newSocket(provider));
    }

    /**
     * Create a new instance using the given {@link ServerSocketChannel}.
     */
    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }

?其中的SelectionKey.OP_ACCEPT最為關(guān)鍵,Netty正是在此處將NioServerSocketChannel的read事件定義為NIO底層的OP_ACCEPT,統(tǒng)一完成read事件的抽象。
?至此,我們已分析完兩條線索中的服務(wù)端部分,下面分析客戶端部分。首先是AbstractNioChannel的另一個子類AbstractNioByteChannel。

5.5 AbstractNioByteChannel源碼分析

?從字面可推知,AbstractNioByteChannel的底層數(shù)據(jù)為Byte字節(jié)。首先看構(gòu)造方法:

/**
     * Create a new instance
     *
     * @param parent            the parent {@link Channel} by which this instance was created. May be {@code null}
     * @param ch                the underlying {@link SelectableChannel} on which it operates
     */
    protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, SelectionKey.OP_READ);
    }

?其中的SelectionKey.OP_READ,說明AbstractNioByteChannel的read事件為NIO底層的OP_READ事件。
?然后我們看read事件框架:

@Override
        public final void read() {
            final ChannelConfig config = config();
            if (shouldBreakReadReady(config)) {
                clearReadPending();
                return;
            }
            final ChannelPipeline pipeline = pipeline();
            final ByteBufAllocator allocator = config.getAllocator();
            final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
            allocHandle.reset(config);

            ByteBuf byteBuf = null; // 創(chuàng)建一個ByteBuf
            boolean close = false;
            try {
                do {
                    byteBuf = allocHandle.allocate(allocator); // 創(chuàng)建一個ByteBuf
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));  // doReadBytes模板方法,子類實現(xiàn)細(xì)節(jié)
                    if (allocHandle.lastBytesRead() <= 0) { // 沒有數(shù)據(jù)可讀
                        // nothing was read. release the buffer.
                        byteBuf.release();
                        byteBuf = null;
                        close = allocHandle.lastBytesRead() < 0; // 讀取數(shù)據(jù)量為負(fù)數(shù)表示對端已經(jīng)關(guān)閉
                        if (close) {
                            // There is nothing left to read as we received an EOF.
                            readPending = false;
                        }
                        break;
                    }

                    allocHandle.incMessagesRead(1);
                    readPending = false; // 沒有底層讀事件進(jìn)行
                    pipeline.fireChannelRead(byteBuf); // 觸發(fā)ChannelRead事件,用戶處理
                    byteBuf = null;
                } while (allocHandle.continueReading());

                allocHandle.readComplete();
                // ReadComplete結(jié)束時,如果開啟autoRead則會調(diào)用beginRead,從而可以繼續(xù)read
                pipeline.fireChannelReadComplete();

                if (close) {
                    closeOnRead(pipeline);
                }
            } catch (Throwable t) {
                handleReadException(pipeline, byteBuf, t, close, allocHandle);
            } finally {
                // Check if there is a readPending which was not processed yet.
                // This could be for two reasons:
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
                //
                // See https://github.com/netty/netty/issues/2254
                if (!readPending && !config.isAutoRead()) {
                    // 既沒有配置autoRead也沒有底層讀事件進(jìn)行
                    removeReadOp();
                }
            }
        }
    }

?AbstractNioByteChannel的read事件框架處理流程與AbstractNioMessageChannel的稍有不同:AbstractNioMessageChannel依次讀取Message,最后統(tǒng)一觸發(fā)ChannelRead事件;而AbstractNioByteChannel每讀取到一定字節(jié)就觸發(fā)ChannelRead事件。這是因為,AbstractNioMessageChannel需求高吞吐量,特別是ServerSocketChannel需要盡可能多地接受連接;而AbstractNioByteChannel需求快響應(yīng),要盡可能快地響應(yīng)遠(yuǎn)端請求。
?read事件的具體流程請參考代碼和代碼注釋進(jìn)行理解,不再分析。注意到代碼中有關(guān)于接收緩沖區(qū)的代碼,這一部分我們單獨使用一節(jié)講述,之后會分析。當(dāng)讀取到的數(shù)據(jù)小于零時,表示遠(yuǎn)端連接已關(guān)閉,這時會調(diào)用closeOnRead(pipeline)方法:

private void closeOnRead(ChannelPipeline pipeline) {
            if (!isInputShutdown0()) {
                if (isAllowHalfClosure(config())) {
                    shutdownInput();
                    pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
                } else {
                    close(voidPromise()); // 直接關(guān)閉
                }
            } else {
                inputClosedSeenErrorOnRead = true;
                pipeline.fireUserEventTriggered(ChannelInputShutdownReadComplete.INSTANCE);
            }
        }

?這段代碼正是Channel參數(shù)ALLOW_HALF_CLOSURE的意義描述,該參數(shù)為True時,會觸發(fā)用戶事件ChannelInputShutdownEvent,否則,直接關(guān)閉該Channel。拋出異常時,會調(diào)用handleReadException(pipeline, byteBuf, t, close)方法:

private void handleReadException(ChannelPipeline pipeline, ByteBuf byteBuf, Throwable cause, boolean close,
                RecvByteBufAllocator.Handle allocHandle) {
            if (byteBuf != null) { // 已讀取到數(shù)據(jù)
                if (byteBuf.isReadable()) { // 數(shù)據(jù)可讀
                    readPending = false;
                    pipeline.fireChannelRead(byteBuf);
                } else { // 數(shù)據(jù)不可讀
                    byteBuf.release();
                }
            }
            allocHandle.readComplete();
            pipeline.fireChannelReadComplete();
            pipeline.fireExceptionCaught(cause);
            if (close || cause instanceof IOException) {
                closeOnRead(pipeline);
            }
        }

?可見,拋出異常時,如果讀取到可用數(shù)據(jù)和正常讀取一樣觸發(fā)ChannelRead事件,只是最后會統(tǒng)一觸發(fā)ExceptionCaught事件由用戶進(jìn)行處理。
?至此,read事件框架分析完畢,下面我們分析write事件的細(xì)節(jié)實現(xiàn)方法doWrite()。在此之前,先看filterOutboundMessage()方法對需要寫的數(shù)據(jù)進(jìn)行過濾。

@Override
    protected final Object filterOutboundMessage(Object msg) {
        if (msg instanceof ByteBuf) {
            ByteBuf buf = (ByteBuf) msg;
            if (buf.isDirect()) {
                return msg;
            }

            return newDirectBuffer(buf); // 非DirectBuf轉(zhuǎn)為DirectBuf
        }

        if (msg instanceof FileRegion) {
            return msg;
        }

        throw new UnsupportedOperationException(
                "unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES);
    }

?可知,Netty支持的寫數(shù)據(jù)類型只有兩種:DirectBuffer和FileRegion。我們再看這些數(shù)據(jù)怎么寫到Channel上,也就是doWrite()方法:

 /**
     * Write objects to the OS.
     * @param in the collection which contains objects to write.
     * @return The value that should be decremented from the write quantum which starts at
     * {@link ChannelConfig#getWriteSpinCount()}. The typical use cases are as follows:
     * <ul>
     *     <li>0 - if no write was attempted. This is appropriate if an empty {@link ByteBuf} (or other empty content)
     *     is encountered</li>
     *     <li>1 - if a single call to write data was made to the OS</li>
     *     <li>{@link ChannelUtils#WRITE_STATUS_SNDBUF_FULL} - if an attempt to write data was made to the OS, but no
     *     data was accepted</li>
     * </ul>
     * @throws Exception if an I/O exception occurs during write.
     */
    protected final int doWrite0(ChannelOutboundBuffer in) throws Exception {
        Object msg = in.current();
        if (msg == null) {
            // Directly return here so incompleteWrite(...) is not called.
            return 0;
        }
        return doWriteInternal(in, in.current());
    }

    private int doWriteInternal(ChannelOutboundBuffer in, Object msg) throws Exception {
        if (msg instanceof ByteBuf) {
            ByteBuf buf = (ByteBuf) msg;
            if (!buf.isReadable()) {
                in.remove();
                return 0;
            }

            final int localFlushedAmount = doWriteBytes(buf); // 模板方法,子類實現(xiàn)細(xì)節(jié)
            if (localFlushedAmount > 0) {
                in.progress(localFlushedAmount); // 記錄進(jìn)度
                if (!buf.isReadable()) {
                    in.remove();  // 完成時,清理緩沖區(qū)
                }
                return 1; // 跳出循環(huán)執(zhí)行incompleteWrite()
            }
        } else if (msg instanceof FileRegion) {
            FileRegion region = (FileRegion) msg;
            if (region.transferred() >= region.count()) {
                in.remove();
                return 0; // 跳出循環(huán)執(zhí)行incompleteWrite()
            }

            long localFlushedAmount = doWriteFileRegion(region);
            if (localFlushedAmount > 0) {
                in.progress(localFlushedAmount); // 記錄進(jìn)度
                if (region.transferred() >= region.count()) {
                    in.remove();
                }
                return 1;
            }
        } else {
            // Should not reach here.
            throw new Error(); // 其他類型不支持
        }
        return WRITE_STATUS_SNDBUF_FULL;
    }

    @Override
    protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        int writeSpinCount = config().getWriteSpinCount();
        do {
            Object msg = in.current();
            if (msg == null) { // 數(shù)據(jù)已全部寫完
                // Wrote all messages.
                clearOpWrite();  // 清除OP_WRITE事件
                // Directly return here so incompleteWrite(...) is not called.
                return;
            }
            writeSpinCount -= doWriteInternal(in, msg);
        } while (writeSpinCount > 0);

        incompleteWrite(writeSpinCount < 0);
    }

    @Override
    protected final Object filterOutboundMessage(Object msg) {
        if (msg instanceof ByteBuf) {
            ByteBuf buf = (ByteBuf) msg;
            if (buf.isDirect()) {
                return msg;
            }

            return newDirectBuffer(buf); // 非DirectBuf轉(zhuǎn)為DirectBuf
        }

        if (msg instanceof FileRegion) {
            return msg;
        }

        throw new UnsupportedOperationException(
                "unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES);
    }

?代碼中省略了對FileRegion的處理,F(xiàn)ileRegion是Netty對NIO底層的FileChannel的封裝,負(fù)責(zé)將File中的數(shù)據(jù)寫入到WritableChannel中。FileRegion的默認(rèn)實現(xiàn)是DefaultFileRegion,如果你很感興趣它的實現(xiàn),可以自行查閱。
我們主要分析對ByteBuf的處理。doWrite的流程簡潔明了,核心操作是模板方法doWriteBytes(buf),將ByteBuf中的數(shù)據(jù)寫入到Channel,由于NIO底層的寫操作返回已寫入的數(shù)據(jù)量,在非阻塞模式下該值可能為0,此時會調(diào)用incompleteWrite()方法:

protected final void incompleteWrite(boolean setOpWrite) {
        // Did not write completely.
        if (setOpWrite) {
            setOpWrite();  // 設(shè)置繼續(xù)關(guān)心OP_WRITE事件
        } else {
            // It is possible that we have set the write OP, woken up by NIO because the socket is writable, and then
            // use our write quantum. In this case we no longer want to set the write OP because the socket is still
            // writable (as far as we know). We will find out next time we attempt to write if the socket is writable
            // and set the write OP if necessary.
            clearOpWrite();

            // Schedule flush again later so other tasks can be picked up in the meantime
            eventLoop().execute(flushTask); // 再次提交一個flush()任務(wù)
        }
    }

?該方法分兩種情況處理,在上文提到的第一種情況(實際寫0數(shù)據(jù))下,設(shè)置SelectionKey繼續(xù)關(guān)心OP_WRITE事件從而繼續(xù)進(jìn)行寫操作;第二種情況下,也就是寫操作進(jìn)行次數(shù)達(dá)到配置中的writeSpinCount值但尚未寫完,此時向EventLoop提交一個新的flush任務(wù),此時可以響應(yīng)其他請求,從而提交響應(yīng)速度。這樣的處理,不會使大數(shù)據(jù)的寫操作占用全部資源而使其他請求得不到響應(yīng),可見這是一個較為公平的處理。這里引出一個問題:使用Netty如何搭建高性能文件服務(wù)器?
至此,已分析完對于Byte數(shù)據(jù)的read事件和doWrite細(xì)節(jié)的處理,接下里,繼續(xù)分析NioSocketChannel,從而完善各事件框架的細(xì)節(jié)部分。

5.6 NioSocketChannel源碼分析

?NioSocketChannel作為Channel的最末端子類,實現(xiàn)了NioSocket相關(guān)的最底層細(xì)節(jié)實現(xiàn),首先看doBind():

    @Override
    protected void doBind(SocketAddress localAddress) throws Exception {
        doBind0(localAddress);
    }

    private void doBind0(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) { // JDK版本1.7以上
            SocketUtils.bind(javaChannel(), localAddress);
        } else {
            SocketUtils.bind(javaChannel().socket(), localAddress);
        }
    }

?這部分代碼與NioServerSocketChannel中相同,委托給JDK的Channel進(jìn)行綁定操作。
接著再看doConnect()和doFinishConnect()方法:

@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) {
                // 設(shè)置關(guān)心OP_CONNECT事件,事件就緒時調(diào)用finishConnect()
                selectionKey().interestOps(SelectionKey.OP_CONNECT);
            }
            success = true;
            return connected;
        } finally {
            if (!success) {
                doClose();
            }
        }
    }

    @Override
    protected void doFinishConnect() throws Exception {
        if (!javaChannel().finishConnect()) {
            throw new Error();
        }
    }

?JDK中的Channel在非阻塞模式下調(diào)用connect()方法時,會立即返回結(jié)果:成功建立連接返回True,操作還在進(jìn)行時返回False。返回False時,需要在底層OP_CONNECT事件就緒時,調(diào)用finishConnect()方法完成連接操作。
再看doDisconnect()和doClose()方法:

@Override
    protected void doDisconnect() throws Exception {
        doClose();
    }

    @Override
    protected void doClose() throws Exception {
        super.doClose(); // AbstractNioChannel中關(guān)于連接超時的處理
        javaChannel().close();
    }

?然后看核心的doReadBytes()和doWriteXXX()方法:

@Override
    protected int doReadBytes(ByteBuf byteBuf) throws Exception {
        final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
        allocHandle.attemptedBytesRead(byteBuf.writableBytes());
        return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
    }

    @Override
    protected int doWriteBytes(ByteBuf buf) throws Exception {
        final int expectedWrittenBytes = buf.readableBytes();
        return buf.readBytes(javaChannel(), expectedWrittenBytes);
    }

    @Override
    protected long doWriteFileRegion(FileRegion region) throws Exception {
        final long position = region.transferred();
        return region.transferTo(javaChannel(), position);
    }

?對于read和write操作,委托給ByteBuf處理,我們將使用專門的一章,對這一部分細(xì)節(jié)進(jìn)行完善,將在后面介紹。
NioSocketChannel最重要的部分是覆蓋了父類的doWrite()方法,使用更高效的方式進(jìn)行寫操作,其代碼如下:

@Override
    protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        SocketChannel ch = javaChannel();
        int writeSpinCount = config().getWriteSpinCount();
        do {
            if (in.isEmpty()) {
                // All written so clear OP_WRITE
                clearOpWrite(); // 所有數(shù)據(jù)已寫完,不再關(guān)心OP_WRITE事件
                // Directly return here so incompleteWrite(...) is not called.
                return;
            }

            // Ensure the pending writes are made of ByteBufs only.
            int maxBytesPerGatheringWrite = ((NioSocketChannelConfig) config).getMaxBytesPerGatheringWrite();
            ByteBuffer[] nioBuffers = in.nioBuffers(1024, maxBytesPerGatheringWrite);
            int nioBufferCnt = in.nioBufferCount();

            // Always us nioBuffers() to workaround data-corruption.
            // See https://github.com/netty/netty/issues/2761
            switch (nioBufferCnt) {
                case 0: // 沒有ByteBuffer,也就是只有FileRegion
                    // We have something else beside ByteBuffers to write so fallback to normal writes.
                    writeSpinCount -= doWrite0(in); // 使用父類方法進(jìn)行普通處理
                    break;
                case 1: { // 只有一個ByteBuffer,此時的處理等效于父類方法的處理
                    // Only one ByteBuf so use non-gathering write
                    // Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is no need
                    // to check if the total size of all the buffers is non-zero.
                    ByteBuffer buffer = nioBuffers[0];
                    int attemptedBytes = buffer.remaining();
                    final int localWrittenBytes = ch.write(buffer);
                    if (localWrittenBytes <= 0) {
                        incompleteWrite(true);
                        return;
                    }
                    adjustMaxBytesPerGatheringWrite(attemptedBytes, localWrittenBytes, maxBytesPerGatheringWrite);
                    in.removeBytes(localWrittenBytes);
                    --writeSpinCount;
                    break;
                }
                default: { // 多個ByteBuffer,采用gathering方法處理
                    // Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is no need
                    // to check if the total size of all the buffers is non-zero.
                    // We limit the max amount to int above so cast is safe
                    long attemptedBytes = in.nioBufferSize();
                    // gathering方法,此時一次寫多個ByteBuffer
                    final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt);
                    if (localWrittenBytes <= 0) {
                        incompleteWrite(true);
                        return;
                    }
                    // Casting to int is safe because we limit the total amount of data in the nioBuffers to int above.
                    adjustMaxBytesPerGatheringWrite((int) attemptedBytes, (int) localWrittenBytes,
                            maxBytesPerGatheringWrite);
                    in.removeBytes(localWrittenBytes); // 清理緩沖區(qū)
                    --writeSpinCount;
                    break;
                }
            }
        } while (writeSpinCount > 0);

        incompleteWrite(writeSpinCount < 0); 
    }

?在明白了父類的doWrite方法后,這段代碼便容易理解,本段代碼做的優(yōu)化是:當(dāng)輸出緩沖區(qū)中有多個buffer時,采用Gathering Writes將數(shù)據(jù)從這些buffer寫入到同一個channel。
?在AbstractUnsafe對close事件框架的分析中,有一個prepareToClose()方法,進(jìn)行關(guān)閉的必要處理并在必要時返回一個Executor執(zhí)行doClose()操作,默認(rèn)方法返回null,NioSocketChannelUnsafe覆蓋了父類的實現(xiàn),代碼如下:

@Override
        protected Executor prepareToClose() {
            try {
                if (javaChannel().isOpen() && config().getSoLinger() > 0) {
                    // We need to cancel this key of the channel so we may not end up in a eventloop spin
                    // because we try to read or write until the actual close happens which may be later due
                    // SO_LINGER handling.
                    // See https://github.com/netty/netty/issues/4449
                    doDeregister(); // 取消選擇鍵selectionKey
                    return GlobalEventExecutor.INSTANCE;
                }
            } catch (Throwable ignore) {
                // Ignore the error as the underlying channel may be closed in the meantime and so
                // getSoLinger() may produce an exception. In this case we just return null.
                // See https://github.com/netty/netty/issues/4449
            }
            return null;
        }

?SO_LINGER表示Socket關(guān)閉的延時時間,在此時間內(nèi),內(nèi)核將繼續(xù)把TCP緩沖區(qū)的數(shù)據(jù)發(fā)送給對端且執(zhí)行close操作的線程將阻塞直到數(shù)據(jù)發(fā)送完成。Netty的原則是I/O線程不能被阻塞,所以此時返回一個Executor用于執(zhí)行阻塞的doClose()操作。doDeregister()取消選擇鍵selectionKey是因為:延遲關(guān)閉期間, 如果selectionKey仍然關(guān)心OP_WRITE事件,而輸出緩沖區(qū)又為null,這樣write操作直接返回,不會再執(zhí)行clearOpWrite()操作取消關(guān)心OP_WRITE事件,而Channel一般是可寫的,這樣OP_WRITE事件會不斷就緒從而耗盡CPU,所以需要取消選擇鍵刪除注冊的事件。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。