吃透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();
的時候會執行我們自定義的childHandler
的handlerAdded
方法,會執行ChannelInitializer
的initChannel
方法,講我們自定義的處理器添加進去:
然後出發相應的handlerAdded
,channelRegistered
,channelActive
方法。即處理器添加了,註冊了,又激活了。
完成任務之後,就進入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執行的是AbstractReferenceCountedByteBuf
的touch(java.lang.Object)
,貌似沒啥改變:
最後傳遞到我自定義的處理器中,讀取出來:
我客戶端是用NIO
發的:
總結
基本你上的流程跟上一篇的差不多,只是讀取數據方面有點不一樣。現在我們知道大致的流程了,後面會繼續看細節,其實我講的這些也都是個大概,但是很重要,骨架要清楚,不然開始就我往細節裏鑽,會出不來了。
好了,今天就到這裏了,希望對學習理解有幫助,大神看見勿噴,僅爲自己的學習理解,能力有限,請多包涵。