Netty 之 AbstractNioByteChannel 源碼分析

Netty 版本:4.1.33.Final-SNAPSHOT

AbstractNioByteChannel

public abstract class AbstractNioByteChannel extends AbstractNioChannel {

    // 負責刷新發送緩存鏈表中的數據
    private final Runnable flushTask = new Runnable() {
        @Override
        public void run() {
            // Calling flush0 directly to ensure we not try to flush messages that were added via write(...) in the
            // meantime.
            ((AbstractNioUnsafe) unsafe()).flush0();
        }
    };
    
    // 
    protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, SelectionKey.OP_READ);
    }

    // NioByteUnsafe 重寫了 AbstractNioUnsafe 類中的讀取方法
    @Override
    protected AbstractNioUnsafe newUnsafe() {
        return new NioByteUnsafe();
    }

1、該類定義了一個 flushTask 變量,來負責刷新發送已經 write 到緩存中的數據。write 的數據沒有直接寫到 socket 中,而是寫入到 ChannelOutboundBuffer 緩存中,等 flush 的時候纔會寫到 Socket 中進行發送數據。
2、AbstractNioByteChannel 定義了 NioByteUnsafe 類。
NioByteUnsafe 類繼承了 AbstractNioChannel 的內部類 AbstractNioUnsafe,並重寫了讀取數據的方法。


AbstractNioByteChannel 類主要定義了寫入消息的 doWrite() 方法,下面我們主要分析發送消息的 doWrite() 方法。

doWrite() 方法

@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
    // 獲取循環發送的次數
    int writeSpinCount = config().getWriteSpinCount();
    do {
        // 獲取寫緩存鏈表中第一條要寫入的數據
        Object msg = in.current();
        // 如果沒有要寫入的數據,取消註冊到 selector 上的 OP_WRITE 事件。
        if (msg == null) {
            // Wrote all messages.
            clearOpWrite();
            // Directly return here so incompleteWrite(...) is not called.
            return;
        }
        // 寫入消息
        writeSpinCount -= doWriteInternal(in, msg);
    } while (writeSpinCount > 0);
    // 如果未發送完成則在 selector 上註冊 OP_WRITE 事件。
    // 如果發送完成則在 selector 上取消 OP_WRITE 事件。
    incompleteWrite(writeSpinCount < 0);
}

1、首先獲取循環發送的次數,默認爲16次 private volatile int writeSpinCount = 16。當一次沒有完成該消息的發送的時候(寫半包),會繼續循環發送。
設置發送循環的最大次數原因是當循環發送的時候,I/O 線程會一直嘗試進行寫操作,此時I/O 線程無法處理其他的 I/O 操作,比如發送消息,而客戶端接收數據比較慢,這事會一直不停的嘗試給客戶端發送數據。

2、從 ChannelOutboundBuffer 中獲取待寫入到 Socket 中的消息。
Netty 寫數據的時候首先是把數據寫入到 ChannelOutboundBuffer 緩存中。使用的鏈表保存寫入的消息數據。當調用 flush 的時候會從 ChannelOutboundBuffer 緩存中獲取數據寫入到 Socket 中發送出去。

3、當獲取消息爲空,說明所有數據都已經發送出去。然後調用 clearOpWrite(),取消該 Channel 註冊在 Selector 上的 OP_WRITE 事件。

4、調用 doWriteInternal() 方法寫入消息

5、incompleteWrite() 方法判斷消息是否寫入完成,然後做相關的操作。
如果未發送完成則在 selector 上註冊 OP_WRITE 事件。
如果發送完成則在 selector 上取消 OP_WRITE 事件。

clearOpWrite() 清除寫標識

protected final void clearOpWrite() {
    final 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;
    }
    final int interestOps = key.interestOps();
    if ((interestOps & SelectionKey.OP_WRITE) != 0) {
        key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
    }
}

該方法主要是清除該 Channel 在 Selector 上的註冊的 OP_WRITE 事件。

doWriteInternal() 發送消息

private int doWriteInternal(ChannelOutboundBuffer in, Object msg) throws Exception {
    if (msg instanceof ByteBuf) {
        ByteBuf buf = (ByteBuf) msg;
        // 1
        if (!buf.isReadable()) {
            in.remove();
            return 0;
        }
        // 2
        final int localFlushedAmount = doWriteBytes(buf);
        if (localFlushedAmount > 0) {
            // 3
            in.progress(localFlushedAmount);
            // 4
            if (!buf.isReadable()) {
                in.remove();
            }
            return 1;
        }
    } else if (msg instanceof FileRegion) {
        FileRegion region = (FileRegion) msg;
        if (region.transferred() >= region.count()) {
            in.remove();
            return 0;
        }

        long localFlushedAmount = doWriteFileRegion(region);
        if (localFlushedAmount > 0) {
            in.progress(localFlushedAmount);
            if (region.transferred() >= region.count()) {
                in.remove();
            }
            return 1;
        }
    } else {
        // Should not reach here.
        throw new Error();
    }
    return WRITE_STATUS_SNDBUF_FULL;
}

發送消息可以支持兩種類型 ByteBuf 和 FileRegion。這裏只分析 ByteBuf。FileRegion 和 ByteBuf 發送類似。

1、首先判斷 buf 是否可讀,如果不可讀,說明該消息不可用,直接丟棄,並且在 ChannelOutboundBuffer 的緩存鏈表中刪除該消息 。然後在 doWrite 繼續循環發送下一條消息。
2、如果 buf 可讀,則調用 doWriteBytes() 方法發送消息,直接寫到 Socket 中發送出去,並且返回發送的字節數。
3、如果發送的字節數大於0,則調用 in.progress() 更新消息發送的進度。
4、判斷當前的 buf 中的數據是否已經全部發送完成,如果完成則從 ChannelOutboundBuffer 緩存鏈表中刪除該消息。

該方法的返回值
1、如果從 ChannelOutboundBuffer 中獲取的消息不可讀,返回0,不計入循環發送的次數
2、如果調用 doWriteBytes 發送消息,只要發送的消息字節數大於0,就計入一次循環發送次數
3、如果調用 doWriteBytes 發送消息,發送的字節數爲0,則返回一個WRITE_STATUS_SNDBUF_FULL = Integer.MAX_VALUE值。

一般只有當前 Socket 緩衝區寫滿了,無法再繼續發送數據的時候纔會返回0(Socket 的Buffer已滿)。 如果繼續循環發送也還是無法寫入的,這時只見返回一個比較大值,會直接退出循環發送的,稍後再嘗試寫入。

incompleteWrite()

protected final void incompleteWrite(boolean setOpWrite) {
    // Did not write completely.
    if (setOpWrite) {
        setOpWrite();
    } 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);
    }
}

1、boolean setOpWrite = writeSpinCount < 0; writeSpinCount 什麼時候纔會出現小於0 呢?上面已經分析過,如果調用 doWriteBytes 發送消息,發送的字節數爲0,則返回一個WRITE_STATUS_SNDBUF_FULL = Integer.MAX_VALUE值。Socket 的 Buffer 已經寫滿,無法再繼續發送數據。
這說明該消息還未寫完,然後調用 setOpWrite() 方法,在 Selector 上註冊寫標識。

2、如果寫完,則清除 Selector 上註冊的寫標識。稍後再刷新計劃,以便同時處理其他任務。

protected final void setOpWrite() {
    final 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;
    }
    final int interestOps = key.interestOps();
    if ((interestOps & SelectionKey.OP_WRITE) == 0) {
        key.interestOps(interestOps | SelectionKey.OP_WRITE);
    }
}

首先判斷 SelectionKey 是否有效
判斷 Selector 上是否註冊了 OP_WRITE 標識,如果沒有則註冊上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章