Netty 源碼解析 ——— writeAndFlush流程分析

本文是Netty文集中“Netty 源碼解析”系列的文章。主要對(duì)Netty的重要流程以及類進(jìn)行源碼解析,以使得我們更好的去使用Netty。Netty是一個(gè)非常優(yōu)秀的網(wǎng)絡(luò)框架,對(duì)其源碼解讀的過(guò)程也是不斷學(xué)習(xí)的過(guò)程。

源碼解析

本文主要對(duì)Netty的寫數(shù)據(jù)流程進(jìn)行分析。代碼調(diào)用僅一句:

ctx.writeAndFlush("from server : " + UUID.randomUUID());

變量 ctx 指的是 ChannelHandlerContext對(duì)象,我們跟進(jìn)ChannelHandlerContext的writeAndFlush方法:

public ChannelFuture writeAndFlush(Object msg) {
    return write    AndFlush(msg, newPromise());
}

因?yàn)閷懯钱惒讲僮鳎匀绻覀儧](méi)有自定義一個(gè)ChannelPromise的話,就會(huì)構(gòu)建一個(gè)默認(rèn)的ChannelPromise(即,DefaultChannelPromise)來(lái)表示該異步操作。我們可以通過(guò)往ChannelPromise中注冊(cè)listener來(lái)得到該異步操作的結(jié)果(成功 or 失敗),listener會(huì)在異步操作完成后得到回調(diào)。

往下跟,我們會(huì)到??流程

private void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
    if (invokeHandler()) {
        invokeWrite0(msg, promise);
        invokeFlush0();
    } else {
        writeAndFlush(msg, promise);
    }
}

這里會(huì)完成兩個(gè)重要的步驟:
① invokeWrite0(msg, promise);將消息放入輸出緩沖區(qū)中(ChannelOutboundBuffer)
② invokeFlush0(); 將輸出緩沖區(qū)中的數(shù)據(jù)通過(guò)socket發(fā)送到網(wǎng)絡(luò)中

下面我們來(lái)詳細(xì)展開(kāi)這兩步驟

invokeWrite0(msg, promise)
private void invokeWrite0(Object msg, ChannelPromise promise) {
    try {
        ((ChannelOutboundHandler) handler()).write(this, msg, promise);
    } catch (Throwable t) {
        notifyOutboundHandlerException(t, promise);
    }
}

write是一個(gè)出站事件,它最終會(huì)調(diào)用到ChannelPipeline中head的相關(guān)方法:

public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
    unsafe.write(msg, promise);
}

unsafe是我們構(gòu)建NioServerSocketChannel或NioSocketChannel對(duì)象時(shí),一并構(gòu)建一個(gè)成員屬性,它會(huì)完成底層真正的網(wǎng)絡(luò)操作等。NioServerSocketChannel中持有的unsafe成員變量是NioMessageUnsafe對(duì)象,而NioSocketChannel中持有的unsafe成員變量是NioSocketChannelUnsafe對(duì)象。這里我們要看的是NioSocketChannel的write流程:

public final void write(Object msg, ChannelPromise promise) {
    assertEventLoop();

    ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
    if (outboundBuffer == null) {
        // If the outboundBuffer is null we know the channel was closed and so
        // need to fail the future right away. If it is not null the handling of the rest
        // will be done in flush0()
        // See https://github.com/netty/netty/issues/2362
        safeSetFailure(promise, WRITE_CLOSED_CHANNEL_EXCEPTION);
        // release message now to prevent resource-leak
        ReferenceCountUtil.release(msg);
        return;
    }

    int size;
    try {
        msg = filterOutboundMessage(msg);
        size = pipeline.estimatorHandle().size(msg);
        if (size < 0) {
            size = 0;
        }
    } catch (Throwable t) {
        safeSetFailure(promise, t);
        ReferenceCountUtil.release(msg);
        return;
    }

    outboundBuffer.addMessage(msg, size, promise);
}

① 獲取該NioSocketChannel的ChannelOutboundBuffer成員屬性。(確切地來(lái)說(shuō)ChannelOutboundBuffer是NioSocketChannelUnsafe對(duì)象中的成員屬性,而NioSocketChannelUnsafe才是NioSocketChannel的成員屬性。)每一個(gè)NioSocketChannel會(huì)維護(hù)一個(gè)它們自己的ChannelOutboundBuffer,用于存儲(chǔ)待出站寫請(qǐng)求。
判斷該outboundBuffer是否為null,如果為null則說(shuō)明該NioSocketChannel已經(jīng)關(guān)閉了,那么就會(huì)標(biāo)志該異步寫操作為失敗完成,并釋放寫消息后返回。
② 『msg = filterOutboundMessage(msg);』過(guò)濾待發(fā)送的消息:

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

            return newDirectBuffer(buf);
        }

        if (msg instanceof FileRegion) {
            return msg;
        }

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

過(guò)濾待發(fā)送的消息,只有ByteBuf(堆 or 非堆)以及 FileRegion可以進(jìn)行最終的Socket網(wǎng)絡(luò)傳輸,其他類型的數(shù)據(jù)是不支持的,會(huì)拋UnsupportedOperationException異常。并且會(huì)把堆ByteBuf轉(zhuǎn)換為一個(gè)非堆的ByteBuf返回。也就說(shuō),最后會(huì)通過(guò)socket傳輸?shù)膶?duì)象時(shí)非堆的ByteBuf和FileRegion。
『size = pipeline.estimatorHandle().size(msg);』估計(jì)待發(fā)送數(shù)據(jù)的大小:

public int size(Object msg) {
    if (msg instanceof ByteBuf) {
        return ((ByteBuf) msg).readableBytes();
    }
    if (msg instanceof ByteBufHolder) {
        return ((ByteBufHolder) msg).content().readableBytes();
    }
    if (msg instanceof FileRegion) {
        return 0;
    }
    return unknownSize;
}

估計(jì)待發(fā)送消息數(shù)據(jù)的大小,如果是FileRegion的話直接飯0,否則返回ByteBuf中可讀取字節(jié)數(shù)。
③ 『outboundBuffer.addMessage(msg, size, promise)』將消息加入outboundBuffer中等待發(fā)送。

    public void addMessage(Object msg, int size, ChannelPromise promise) {
        Entry entry = Entry.newInstance(msg, size, total(msg), promise);
        if (tailEntry == null) {
            flushedEntry = null;
            tailEntry = entry;
        } else {
            Entry tail = tailEntry;
            tail.next = entry;
            tailEntry = entry;
        }
        if (unflushedEntry == null) {
            unflushedEntry = entry;
        }

        // increment pending bytes after adding message to the unflushed arrays.
        // See https://github.com/netty/netty/issues/1619
        incrementPendingOutboundBytes(entry.pendingSize, false);
    }

首先對(duì)ChannelOutboundBuffer、Entry做個(gè)簡(jiǎn)單介紹

ChannelOutboundBuffer
一個(gè)內(nèi)部的數(shù)據(jù)結(jié)構(gòu),被AbstractChannel用于存儲(chǔ)它的待出站寫請(qǐng)求。
ChannelOutboundBuffer中有兩個(gè)屬性private Entry unflushedEntry、private Entry flushedEntry。它們都是用Entry對(duì)象通過(guò)next指針來(lái)維護(hù)的一個(gè)單向鏈表。以及一個(gè)private Entry tailEntry;對(duì)象表示始終指向最后一個(gè)Entry對(duì)象(即,最后加入到該ChannelOutboundBuffer中的寫請(qǐng)求的數(shù)據(jù)消息)
unflushedEntry表示還未刷新的ByteBuf的鏈表頭;flushedEntry表示調(diào)用flush()操作時(shí)將會(huì)進(jìn)行刷新的ByteBuf的鏈表頭。

Entry
Entry是ChannelOutboundBunffer的一個(gè)內(nèi)部類,它是對(duì)真實(shí)的寫消息數(shù)據(jù)以及其相關(guān)信息的一個(gè)封裝。大致封裝了如下信息:
a) pendingSize:記錄有該ByteBuf or ByteBufs 中待發(fā)送數(shù)據(jù)大小 和 對(duì)象本身內(nèi)存大小 的累加和;
b) promise:該異步寫操作的ChannelPromise(用于在完成真是的網(wǎng)絡(luò)層write后去標(biāo)識(shí)異步操作的完成以及回調(diào)已經(jīng)注冊(cè)到該promise上的listeners);
c) total:待發(fā)送數(shù)據(jù)包的總大小(該屬性與pendingSize的區(qū)別在于,如果是待發(fā)送的是FileRegion數(shù)據(jù)對(duì)象,則pengdingSize中只有對(duì)象內(nèi)存的大小,即真實(shí)的數(shù)據(jù)大小被記錄為0;但total屬性則是會(huì)記錄FileRegion中數(shù)據(jù)大小,并且total屬性是不包含對(duì)象內(nèi)存大小,僅僅是對(duì)數(shù)據(jù)本身大小的記錄);
e) msg:原始消息對(duì)象的引用;
f) count:寫消息數(shù)據(jù)個(gè)數(shù)的記錄(如果寫消息數(shù)據(jù)是個(gè)數(shù)組的話,該值會(huì)大于1)
這里說(shuō)明下,pendingSize屬性記錄的不單單是寫請(qǐng)求數(shù)據(jù)的大小,記錄的是這個(gè)寫請(qǐng)求對(duì)象的大小。這是什么意思了?這里做個(gè)簡(jiǎn)單的介紹:
一個(gè)對(duì)象占用的內(nèi)存大小除了實(shí)例數(shù)據(jù)(instance data),還包括對(duì)象頭(header)以及對(duì)齊填充(padding)。所以一個(gè)對(duì)象所占的內(nèi)存大小為『對(duì)象頭 + 實(shí)例數(shù)據(jù) + 對(duì)齊填充』,即

entry.pendingSize = size + CHANNEL_OUTBOUND_BUFFER_ENTRY_OVERHEAD;
// Assuming a 64-bit JVM:
//  - 16 bytes object header
//  - 8 reference fields
//  - 2 long fields
//  - 2 int fields
//  - 1 boolean field
//  - padding
static final int CHANNEL_OUTBOUND_BUFFER_ENTRY_OVERHEAD =
        SystemPropertyUtil.getInt("io.netty.transport.outboundBufferEntrySizeOverhead", 96);

??假設(shè)的是64位操作系統(tǒng)下,且沒(méi)有使用各種壓縮選項(xiàng)的情況。對(duì)象頭的長(zhǎng)度占16字節(jié);引用屬性占8字節(jié);long類型占8字節(jié);int類型占4字節(jié);boolean類型占1字節(jié)。同時(shí),由于HotSpot VM的自動(dòng)內(nèi)存管理系統(tǒng)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍,也就是說(shuō)對(duì)象的大小必須是8字節(jié)的整數(shù)倍,如果最終字節(jié)數(shù)不為8的倍數(shù),則padding會(huì)補(bǔ)足至8的倍數(shù)。

static final class Entry {
    private final Handle<Entry> handle;     // reference field ( 8 bytes)
    Entry next;     // reference field ( 8 bytes)
    Object msg;     // reference field ( 8 bytes)
    ByteBuffer[] bufs;     // reference field ( 8 bytes)
    ByteBuffer buf;     // reference field ( 8 bytes)
    ChannelPromise promise;     // reference field ( 8 bytes)
    long progress;     // long field ( 8 bytes)
    long total;     // long field ( 8 bytes)
    int pendingSize;     // int field ( 4 bytes)
    int count = -1;     // int field ( 4 bytes)
    boolean cancelled;     // boolean field ( 1 bytes)

我們根據(jù)上面的理論來(lái)計(jì)算下Entry對(duì)象占用內(nèi)存的大小:
header (16 bytes) + 6 * reference fields(8 bytes)+ 2 * long fields(8 bytes)+ 2 * int fields(4 bytes)+ 1 * boolean field(1 byte)= 89 ——> 加上7bytes的padding = 96 bytes
這就是CHANNEL_OUTBOUND_BUFFER_ENTRY_OVERHEAD默認(rèn)值 96 的由來(lái)。(關(guān)于JVM中對(duì)象的內(nèi)存大小的詳細(xì)分析,歡迎參閱JVM中 對(duì)象的內(nèi)存布局 以及 實(shí)例分析)

addMessage方法主要就是將請(qǐng)求寫出的數(shù)據(jù)封裝為Entry對(duì)象,然后加入到tailEntry和unflushedEntry中。
然后調(diào)用『incrementPendingOutboundBytes(entry.pendingSize, false);』對(duì)totalPendingSize屬性以及unwritable字段做調(diào)整。
totalPendingSize字段記錄了該ChannelOutboundBuffer中所有帶發(fā)送Entry對(duì)象的占的總內(nèi)存大小和所有帶發(fā)送數(shù)據(jù)的大小。unwritable用來(lái)標(biāo)示當(dāng)前該Channel要發(fā)送的數(shù)據(jù)是否已經(jīng)超過(guò)了設(shè)定 or 默認(rèn)的WriteBufferWaterMark的high值。如果當(dāng)前操作導(dǎo)致了待寫出的數(shù)據(jù)(包括Entry對(duì)象大小以及真實(shí)需要傳輸數(shù)據(jù)的大小)超過(guò)了設(shè)置寫緩沖區(qū)的高水位,那么將會(huì)觸發(fā)fireChannelWritabilityChanged事件。

WriteBufferWaterMark
WriteBufferWaterMark用于設(shè)置寫緩存的高水位標(biāo)志和低水位標(biāo)志。
如果寫緩沖區(qū)隊(duì)列中字節(jié)的數(shù)量超過(guò)了設(shè)置的高水位標(biāo)志,那么Channel#isWritable()方法將開(kāi)始返回false。然后當(dāng)寫緩沖區(qū)中的字節(jié)數(shù)量減少至小于了低水位標(biāo)志,Channel#isWritable()方法會(huì)重新開(kāi)始返回true。關(guān)于Channel#isWritable()方法目前主要用在ChunkedWriteHandler以及HTTP2的Handler中。因此,如果你想在程序中通過(guò)設(shè)置WriteBufferWaterMark來(lái)控制數(shù)據(jù)的寫出,但你在程序中并沒(méi)有使用ChunkedWriteHandler或HTTP2,那么這就需要我們自己通過(guò)『Channel#isWritable()』來(lái)實(shí)現(xiàn)是否可用繼續(xù)寫出數(shù)據(jù)。 比如:

protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
    // ......
    if( ctx.channel().isWritable() ) 
    { 
        ctx.writeAndFlush(...) 
    }
}

總的來(lái)說(shuō),在write的操作最終會(huì)將ByteBuf封裝為一個(gè)Entry對(duì)象放到unflushedEntry單向鏈表的尾部(通過(guò)修改tailEntry來(lái)實(shí)現(xiàn)的),并修改用于記錄有該ChannelOutboundBuffer中待發(fā)送Entry對(duì)象總內(nèi)存大小的屬性totalPendingSize字段。

好了,但目前為止write操作就講完了。接下來(lái)我們來(lái)看下flush操作:

invokeFlush0()

flush也是一個(gè)出站事件,它最終會(huì)調(diào)用到ChannelPipeline中head的相關(guān)方法:

public void flush(ChannelHandlerContext ctx) throws Exception {
    unsafe.flush();
}

這里的unsafe成員變量依舊是NioSocketChannelUnsafe對(duì)象,跟進(jìn)去:

public final void flush() {
    assertEventLoop();

    ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
    if (outboundBuffer == null) {
        return;
    }

    outboundBuffer.addFlush();
    flush0();
}

這里主要完成兩個(gè)操作:
① outboundBuffer.addFlush();
添加一個(gè)flush到這個(gè)ChannelOutboundBuffer,這意味著,將在此之前添加的消息標(biāo)記為flushed,你將可以處理這些消息。

    public void addFlush() {
        // There is no need to process all entries if there was already a flush before and no new messages
        // where added in the meantime.
        //
        // See https://github.com/netty/netty/issues/2577
        Entry entry = unflushedEntry;
        if (entry != null) {
            if (flushedEntry == null) {
                // there is no flushedEntry yet, so start with the entry
                flushedEntry = entry;
            }
            do {
                flushed ++;
                if (!entry.promise.setUncancellable()) {
                    // Was cancelled so make sure we free up memory and notify about the freed bytes
                    int pending = entry.cancel();
                    decrementPendingOutboundBytes(pending, false, true);
                }
                entry = entry.next;
            } while (entry != null);

            // All flushed so reset unflushedEntry
            unflushedEntry = null;
        }
    }

a) 如我們前面所說(shuō),write操作最終會(huì)將包含有待發(fā)送消息的ByteBuf封裝成Entry對(duì)象放入unflushedEntry單向鏈表的尾部。而這里就會(huì)先判斷unflushedEntry是否為null,如果為null則說(shuō)明所有的entries已經(jīng)被flush了,并在此期間沒(méi)有新的消息被添加進(jìn)ChannelOutboundBuffer中。所有直接返回就好。
b) 如果unflushedEntry非空,則說(shuō)明有待發(fā)送的entries等待被發(fā)送。那么將unflushedEntry賦值給flushedEntry(調(diào)用flush()操作時(shí)就是將該flushedEntry單向鏈表中的entries的數(shù)據(jù)發(fā)到網(wǎng)絡(luò)),并將unflushedEntry置為null,表示沒(méi)有待發(fā)送的entries了。并通過(guò)flushed成員屬性記錄待發(fā)送entries的個(gè)數(shù)。

② flush0();

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()) {
        return;
    }
    super.flush0();
}

a) 首先通過(guò)isFlushPending()方法來(lái)判斷flush操作是否需要被掛起:

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

也就是說(shuō),首先會(huì)判斷當(dāng)前NioSocketChannel的SelectionKey.OP_WRITE事件是否有被注冊(cè)到對(duì)應(yīng)的Selector上,如果有,則說(shuō)明當(dāng)前寫緩沖區(qū)已經(jīng)滿了(這里指是socket的寫緩沖區(qū)滿了,并且socket并沒(méi)有被關(guān)閉,那么write操作將返回0。這是如果還有未寫出的數(shù)據(jù)待被發(fā)送,那么就會(huì)注冊(cè)SelectionKey.OP_WRITE事件)。等寫緩沖區(qū)有空間時(shí),SelectionKey.OP_WRITE事件就會(huì)被觸發(fā),到時(shí)NioEventLoop的事件循環(huán)就會(huì)調(diào)用forceFlush()方法來(lái)繼續(xù)將為寫出的數(shù)據(jù)寫出,所以這里直接返回就好。
b) 當(dāng)socket寫緩沖區(qū)未滿,那么就執(zhí)行flush0()

protected void flush0() {
    if (inFlush0) {
        // Avoid re-entrance
        return;
    }

    final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
    if (outboundBuffer == null || outboundBuffer.isEmpty()) {
        return;
    }

    inFlush0 = true;

    // Mark all pending write requests as failure if the channel is inactive.
    if (!isActive()) {
        try {
            if (isOpen()) {
                outboundBuffer.failFlushed(FLUSH0_NOT_YET_CONNECTED_EXCEPTION, true);
            } else {
                // Do not trigger channelWritabilityChanged because the channel is closed already.
                outboundBuffer.failFlushed(FLUSH0_CLOSED_CHANNEL_EXCEPTION, false);
            }
        } finally {
            inFlush0 = false;
        }
        return;
    }

    try {
        doWrite(outboundBuffer);
    } catch (Throwable t) {
        if (t instanceof IOException && config().isAutoClose()) {
            /**
             * Just call {@link #close(ChannelPromise, Throwable, boolean)} here which will take care of
             * failing all flushed messages and also ensure the actual close of the underlying transport
             * will happen before the promises are notified.
             *
             * This is needed as otherwise {@link #isActive()} , {@link #isOpen()} and {@link #isWritable()}
             * may still return {@code true} even if the channel should be closed as result of the exception.
             */
            close(voidPromise(), t, FLUSH0_CLOSED_CHANNEL_EXCEPTION, false);
        } else {
            outboundBuffer.failFlushed(t, true);
        }
    } finally {
        inFlush0 = false;
    }
}
  1. 判斷Channel的輸出緩沖區(qū)是否為null或待發(fā)送的數(shù)據(jù)個(gè)數(shù)為0,如果是則直接返回,因?yàn)榇藭r(shí)并沒(méi)有數(shù)據(jù)需要發(fā)送。
  2. 判斷當(dāng)前的NioSocketChannel是否是Inactive狀態(tài),如果是,則會(huì)標(biāo)識(shí)所有等待寫請(qǐng)求為失敗(即所有的write操作的promise都會(huì)是失敗完成),并且如果NioSocketChannel已經(jīng)關(guān)閉了,失敗的原因是“FLUSH0_CLOSED_CHANNEL_EXCEPTION”且不會(huì)回調(diào)注冊(cè)到promise上的listeners;但如果NioSocketChannel還是open的,則失敗的原始是“FLUSH0_NOT_YET_CONNECTED_EXCEPTION”并且會(huì)回調(diào)注冊(cè)到promise上的listeners。
  3. 調(diào)用doWrite(outboundBuffer);方法將Channel輸出緩沖區(qū)中的數(shù)據(jù)通過(guò)socket傳輸給對(duì)端:


    doWrite是一個(gè)寫循環(huán)操作,當(dāng)滿足一定條件時(shí)會(huì)結(jié)束循環(huán)。每一次循環(huán)會(huì)完成的操作:

  1. 判斷當(dāng)前ChannelOutboundBuffer中的數(shù)據(jù)都已經(jīng)被傳輸完了,如果已經(jīng)傳輸完了,并且發(fā)現(xiàn)NioSocketChannel還注冊(cè)有SelectionKey.OP_WRITE事件,則將SelectionKey.OP_WRITE從感興趣的事件中移除,即,Selector不在監(jiān)聽(tīng)該NioSocketChannel的可寫事件了。然后跳出循環(huán),方法返回。
  2. 初始化writtenBytes = 0、done = false、setOpWrite = false三個(gè)屬性,它們分別表示本次循環(huán)已經(jīng)寫出的字節(jié)數(shù)、本次循環(huán)是否寫出了所有待寫出的數(shù)據(jù)、是否需要設(shè)置SelectionKey.OP_WRITE事件的標(biāo)志為。
  3. 『ByteBuffer[] nioBuffers = in.nioBuffers()』
    獲取所有待寫出的ByteBuffer,它會(huì)將ChannelOutboundBuffer中所有待寫出的ByteBuf轉(zhuǎn)換成JDK Bytebuffer(因?yàn)椋讓右琅f是基于JDK NIO的網(wǎng)絡(luò)傳輸,所有最終傳輸?shù)倪€是JDK 的ByteBuffer對(duì)象)。它依次出去每個(gè)待寫的ByteBuf,然后根據(jù)ByteBuf的信息構(gòu)建一個(gè)ByteBuffer(這里的ByteBuf是一個(gè)堆外ByteBuf,因此構(gòu)建出來(lái)的ByteBuffer也是一個(gè)堆外的ByteBuffer),并設(shè)置該ByteBuffer的readerIndex、readableBytes的值為ByteBuf對(duì)應(yīng)的值。然后返回構(gòu)建好的ByteBuffer[]數(shù)組。
  4. 獲取本次循環(huán)需要寫出的ByteBuffer個(gè)數(shù)
  5. 獲取本次循環(huán)總共需要寫出的數(shù)據(jù)的字節(jié)總數(shù)
  6. 根據(jù)nioBufferCnt值的不同執(zhí)行不同的傳輸流程:
    [1] nioBufferCnt == 0 :對(duì)非ByteBuffer對(duì)象的數(shù)據(jù)進(jìn)行普通的寫操作。
    上面我們說(shuō)了in.nioBuffers()會(huì)將ChannelOutboundBuffer中所有待發(fā)送的ByteBuf轉(zhuǎn)換成Bytebuffer后返回一個(gè)ByteBuffer[]數(shù)組,以便后面進(jìn)行ByteBuffer的傳輸,而nioBufferCnt則表示待發(fā)送ByteBuffer的個(gè)數(shù),即ByteBuffer[]數(shù)組的長(zhǎng)度。注意,這里nioBuffers()僅僅是對(duì)ByteBuf對(duì)象進(jìn)行了操作,但是我們從前面的流程可以得知,除了ByteBuf外FileRegion對(duì)象也是可以進(jìn)行底層的網(wǎng)絡(luò)傳輸?shù)摹R虼水?dāng)待傳輸?shù)膶?duì)象是FileRegion時(shí)“nioBufferCnt == 0”,那么這是就會(huì)調(diào)用『AbstractNioByteChannel#doWrite(ChannelOutboundBuffer in)』方法來(lái)完成數(shù)據(jù)的傳輸。實(shí)際上底層就是依靠JDK NIO 的 FileChannel來(lái)實(shí)現(xiàn)零拷貝的數(shù)據(jù)傳輸。
    [2] nioBufferCnt == 1 :說(shuō)明只有一個(gè)ByteBuffer等待被傳輸,那么不使用gather的write操作來(lái)傳輸數(shù)據(jù)(JDK NIO 支持一次寫單個(gè)ByteBuffer 以及 一次寫多個(gè)ByteBuffer的聚集寫模式)
    [3] nioBufferCnt > 1 :說(shuō)明有多個(gè)ByteBuffer等待被傳輸,那么使用JDK NIO的聚集寫操作,一次性傳輸多個(gè)ByteBuffer到NioSocketChannel中。

[2]、[3] 中寫操作的邏輯是一樣的:

for (int i = config().getWriteSpinCount() - 1; i >= 0; i --) {
    final long localWrittenBytes = ch.write(...);
    if (localWrittenBytes == 0) {
        setOpWrite = true;
        break;
    }
    expectedWrittenBytes -= localWrittenBytes;
    writtenBytes += localWrittenBytes;
    if (expectedWrittenBytes == 0) {
        done = true;
        break;
    }
}

config().getWriteSpinCount()為16,也就是一次寫操作會(huì)最多執(zhí)行16次的SocketChannel.write操作來(lái)將數(shù)據(jù)寫到網(wǎng)絡(luò)中。每次ch.write完都會(huì)進(jìn)行相應(yīng)的『expectedWrittenBytes -= localWrittenBytes;』操作,將expectedWrittenBytes期待被寫的字節(jié)數(shù)減去已經(jīng)寫出的字節(jié)數(shù)。如果在最后expectedWrittenBytes依舊大于0,則說(shuō)明在這16次的socket寫操作后依舊還有未寫完的數(shù)據(jù)等待被繼續(xù)寫,那么done就會(huì)為false;否則若所有的數(shù)據(jù)都寫完了,done會(huì)被置為true。注意,ch.write操作會(huì)返回本次寫操作寫出的字節(jié)數(shù),但該方法返回0時(shí),即localWrittenBytes為0,則說(shuō)明底層的寫緩沖區(qū)已經(jīng)滿了(這里應(yīng)該指的是linux底層的寫緩沖區(qū)滿了),這是就會(huì)將setOpWrite置為true,此時(shí)因?yàn)閿?shù)據(jù)還沒(méi)寫完done還是false。那么這種情況下就會(huì)注冊(cè)當(dāng)前SocketChannel的寫事件(SelectionKey.OP_WRITE)到對(duì)應(yīng)的Selector為感興趣的事件,這樣當(dāng)寫緩沖區(qū)有空間時(shí),就會(huì)觸發(fā)SelectionKey.OP_WRITE就緒事件, NioEventLoop的事件循環(huán)在處理SelectionKey.OP_WRITE事件時(shí)會(huì)執(zhí)行forceFlush()以繼續(xù)發(fā)送外發(fā)送完的數(shù)據(jù)。

  1. 『in.removeBytes(writtenBytes)』:釋放所有已經(jīng)寫出去的緩存對(duì)象,并修改部分寫緩沖的索引。
public void removeBytes(long writtenBytes) {
    for (;;) {
        Object msg = current();
        if (!(msg instanceof ByteBuf)) {
            assert writtenBytes == 0;
            break;
        }

        final ByteBuf buf = (ByteBuf) msg;
        final int readerIndex = buf.readerIndex();
        final int readableBytes = buf.writerIndex() - readerIndex;

        if (readableBytes <= writtenBytes) {
            if (writtenBytes != 0) {
                progress(readableBytes);
                writtenBytes -= readableBytes;
            }
            remove();
        } else { // readableBytes > writtenBytes
            if (writtenBytes != 0) {
                buf.readerIndex(readerIndex + (int) writtenBytes);
                progress(writtenBytes);
            }
            break;
        }
    }
    clearNioBuffers();
}

通過(guò)已經(jīng)寫出數(shù)據(jù)的字節(jié)數(shù)來(lái)清理或修改ByteBuf。也就是說(shuō)writtenBytes的大小可能是包含了多個(gè)ByteBuf以及某個(gè)ByteBuf的部分?jǐn)?shù)據(jù)(因?yàn)橐粋€(gè)ByteBuf可能只寫出了部分?jǐn)?shù)據(jù),還未完成被寫出到網(wǎng)絡(luò)層中)。
a) 『 if (readableBytes <= writtenBytes) 』這個(gè)if判斷表示:本次socket的write操作(這里是真的是網(wǎng)絡(luò)通信寫操作了)已經(jīng)寫出去的字節(jié)數(shù)”大于"了當(dāng)前ByteBuf包可讀取的字節(jié)數(shù)。 這說(shuō)明,當(dāng)前這個(gè)包中所有的可寫的數(shù)據(jù)都已經(jīng)寫完了,既然當(dāng)前這個(gè)ByteBuf的數(shù)據(jù)都寫完了,那么久可以將其刪除了。即,調(diào)用『remove()』操作,這個(gè)操作就會(huì)標(biāo)識(shí)異步write操作為成功完成,并且會(huì)回調(diào)已經(jīng)注冊(cè)到ByteBuf的promise上的所有l(wèi)isteners。同時(shí)會(huì)原子的修改ChannelOutboundBuffer的totalPendingSize屬性值,減少已經(jīng)寫出的數(shù)據(jù)大小(包括Entry對(duì)象內(nèi)存大小和真實(shí)數(shù)據(jù)的大小),并且如果減少后totalPendingSize小于設(shè)置 or 默認(rèn)的WriteBufferWaterMark的low值,并且再次之前totalPendingSize超過(guò)了WriteBufferWaterMark的high值,那么將觸發(fā)fireChannelWritabilityChanged事件。『remove()』操作還會(huì)將當(dāng)前的ByteBuf指向下一個(gè)待處理的ByteBuf,最后釋放這個(gè)已經(jīng)被寫出去的ByteBuf對(duì)象資源。
b) 通過(guò)上面的分析,我們知道大數(shù)據(jù)包走的是else流程。也就是說(shuō),本次真實(shí)寫出去的數(shù)據(jù) 比 當(dāng)前這個(gè)ByteBuf的可讀取數(shù)據(jù)要小(也就說(shuō)明,當(dāng)前這個(gè)ByteBuf還沒(méi)有被完全的寫完。因此并不會(huì)通過(guò)調(diào)用『remove()』操作。直到整個(gè)大數(shù)據(jù)包所有的內(nèi)容都寫出去了,那么這是if(readableBytes <= writtenBytes)才會(huì)為真執(zhí)行『remove()』完成相關(guān)后續(xù)的操作)。那么此時(shí),會(huì)根據(jù)已經(jīng)寫出的字節(jié)數(shù)大小修改該ByteBuf的readerIndex索引值。并且,如果該異步寫操作的ChannelPromise是ChannelProgressivePromise對(duì)象并且注冊(cè)了相應(yīng)的progressiveListeners事件,則該listener會(huì)得到回調(diào)。你可以通過(guò)該listener來(lái)觀察到大數(shù)據(jù)包寫出去的進(jìn)度。

  1. done表示本次寫操作是否完成,。有兩種情況下done為false:
    [1] 還有未寫完的數(shù)據(jù)待發(fā)送,并且寫緩沖區(qū)已經(jīng)滿了(這里指的是linux底層的寫緩沖區(qū)滿了),無(wú)法再繼續(xù)寫出,那么此時(shí)setOpWrite標(biāo)識(shí)為true。這種情況下就會(huì)注冊(cè)當(dāng)前SocketChannel的寫事件(SelectionKey.OP_WRITE)到對(duì)應(yīng)的Selector為感興趣的事件,這樣當(dāng)寫緩沖區(qū)有空間時(shí),就會(huì)觸發(fā)SelectionKey.OP_WRITE就緒事件, NioEventLoop的事件循環(huán)在處理SelectionKey.OP_WRITE事件時(shí)會(huì)執(zhí)行forceFlush()以繼續(xù)發(fā)送外發(fā)送完的數(shù)據(jù)。接著退出doWrite()循環(huán)寫操作。
    [2] 執(zhí)行了config().getWriteSpinCount()次(默認(rèn)16次)socket寫操作后,數(shù)據(jù)仍舊未寫完,那么此時(shí)會(huì)將flush()操作封裝成一個(gè)task提交至NioEventLoop的taskQueue中,這樣在NioEventLoop的下一次事件循環(huán)時(shí)會(huì)就會(huì)取出該任務(wù)并執(zhí)行,也就會(huì)繼續(xù)寫出未寫完的任務(wù)了。這也說(shuō)明了,如果發(fā)送的是很大的數(shù)據(jù)包的話,可能一次寫循環(huán)操作是無(wú)法將數(shù)據(jù)全部發(fā)送出去的,也不會(huì)為了發(fā)送該大數(shù)據(jù)包的數(shù)據(jù)而導(dǎo)致NioEventLoop線程的阻塞以至于影響NioEventLoop上其他Channel的操作和響應(yīng)。接著退出doWrite()循環(huán)寫操作。

好了,到目前為止,Netty整個(gè)的寫流程就分析完了。本文主要專注于寫操作的流程,而并未到Netty的內(nèi)存模式進(jìn)行展開(kāi)。

后記

若文章有任何錯(cuò)誤,望大家不吝指教:)

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

推薦閱讀更多精彩內(nèi)容