吃透Netty源碼系列七之worker組註冊NioSocketChannel

worker組註冊NioSocketChannel

先補一張比較好理解的註冊圖,這裏暫且把所有新的通道註冊到同一個worker組的某個事件循環上,實際上是負載均衡註冊的:
在這裏插入圖片描述
註冊其實跟boss組一樣的,只是這個時候參數是NioSocketChannel類型,會從worker組的NioEventLoop選一個進行註冊:
在這裏插入圖片描述
這裏要注意的是,裏面的Unsafe其實是NioSocketChannel.NioSocketChannelUnsafe類型的,繼承了NioByteUnsafe一看這個就知道是跟字節相關的,所以是用於讀取和寫入數據:
在這裏插入圖片描述
我們有看到兩個unsafe子類,上面那個是NioServerSocketChannel用的,下面的是NioSocketChannel的,其實他們只是read不一樣:
在這裏插入圖片描述
register是父類的方法一樣的。跟前面的NioServerSocketChannel註冊一樣的,就是提交註冊任務,開啓線程,你可以看到worker組的線程開啓來了:
在這裏插入圖片描述
之後就是worker組的線程進行執行註冊任務,像選擇器註冊SocketChannel實例:
在這裏插入圖片描述
然後進行pipeline.invokeHandlerAddedIfNeeded();的時候會執行我們自定義的childHandlerhandlerAdded方法,會執行ChannelInitializerinitChannel方法,講我們自定義的處理器添加進去:
在這裏插入圖片描述
然後出發相應的handlerAddedchannelRegisteredchannelActive方法。即處理器添加了,註冊了,又激活了。
完成任務之後,就進入select阻塞了。

讀取消息

一旦建立好的連接發送消息過來,就會處理,跟前面一樣調用processSelectedKeys等一系列方法,直到read方法,主要還是doReadBytes(byteBuf)方法:

  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;
            boolean close = false;
            try {
                do {
                    byteBuf = allocHandle.allocate(allocator);
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));//這裏是關鍵,讀取出具
                    if (allocHandle.lastBytesRead() <= 0) {
                        // nothing was read. release the buffer.
                        byteBuf.release();
                        byteBuf = null;
                        close = allocHandle.lastBytesRead() < 0;
                        if (close) {
                            // There is nothing left to read as we received an EOF.
                            readPending = false;
                        }
                        break;
                    }

                    allocHandle.incMessagesRead(1);
                    readPending = false;
                    pipeline.fireChannelRead(byteBuf);//傳遞讀事件
                    byteBuf = null;
                } while (allocHandle.continueReading());

                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

                if (close) {
                    closeOnRead(pipeline);
                }
            } catch (Throwable t) {
                handleReadException(pipeline, byteBuf, t, close, allocHandle);
            } finally {
                // See https://github.com/netty/netty/issues/2254
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }

doReadBytes

    @Override
    protected int doReadBytes(ByteBuf byteBuf) throws Exception {
        final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();//獲取分配處理器
        allocHandle.attemptedBytesRead(byteBuf.writableBytes());//設置可寫的字節
        return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());//讀取通道的數據,寫入字節緩衝區
    }

writeBytes

向緩衝區寫入數據這個是關鍵:

    @Override
    public int writeBytes(ScatteringByteChannel in, int length) throws IOException {
        ensureWritable(length);
        int writtenBytes = setBytes(writerIndex, in, length);
        if (writtenBytes > 0) {
            writerIndex += writtenBytes;
        }
        return writtenBytes;
    }

setBytes

in.read就是通道從底層去讀取socket緩衝區的數據到字節緩衝區裏:

  @Override
    public final int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
        try {
            return in.read(internalNioBuffer(index, length));
        } catch (ClosedChannelException ignored) {
            return -1;
        }
    }
internalNioBuffer

這個方法其實返回的是ByteBuffer ,也就是說底層是封裝了ByteBuffer 的:

 @Override
    public final ByteBuffer internalNioBuffer(int index, int length) {
        checkIndex(index, length);
        return _internalNioBuffer(index, length, false);
    }
_internalNioBuffer

果然是這樣:

final ByteBuffer _internalNioBuffer(int index, int length, boolean duplicate) {
        index = idx(index);
        ByteBuffer buffer = duplicate ? newInternalNioBuffer(memory) : internalNioBuffer();
        buffer.limit(index + length).position(index);
        return buffer;
    }

而且是直接緩衝區DirectByteBuffer,少了一次從內核到用戶空間的數據拷貝:
在這裏插入圖片描述

allocHandle.lastBytesRead(doReadBytes(byteBuf))

之後就做一些記錄,比如這次我們讀了11個字節,爲什麼是11個呢,因爲我客戶端發送了個hello world,後面會看到:
在這裏插入圖片描述

pipeline.fireChannelRead(byteBuf)

接着就是把緩衝區數據傳遞到管道里,讓處理器處理啦,這個上一篇講過,怎麼傳遞的,就不多說了,但是這裏要注意的是:
在這裏插入圖片描述
如果是引用計數累心的話,會進行封裝:
在這裏插入圖片描述
在這裏插入圖片描述
我們可以看到,netty自定義的所有的字節緩衝區都是引用計數類型的:
在這裏插入圖片描述
所以最後touch執行的是AbstractReferenceCountedByteBuftouch(java.lang.Object),貌似沒啥改變:
在這裏插入圖片描述
最後傳遞到我自定義的處理器中,讀取出來:
在這裏插入圖片描述
我客戶端是用NIO發的:
在這裏插入圖片描述

總結

基本你上的流程跟上一篇的差不多,只是讀取數據方面有點不一樣。現在我們知道大致的流程了,後面會繼續看細節,其實我講的這些也都是個大概,但是很重要,骨架要清楚,不然開始就我往細節裏鑽,會出不來了。

好了,今天就到這裏了,希望對學習理解有幫助,大神看見勿噴,僅爲自己的學習理解,能力有限,請多包涵。

發佈了153 篇原創文章 · 獲贊 46 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章