netty源碼分析9-Unsafe

本文分享以下內容

  1. Unsafe概況
  2. AbstractUnsafe 分析
  3. AbstractNioUnsafe分析
  4. NioByteUnsafe分析
  5. NioMessageUnsafe分析

Unsafe概況

 

unsafe是Channel的內部邏輯實現,實際的IO操作都是由unsafe完成的。

unsafe調用原生的NIO API處理IO操作,使用Pipeline傳遞觸發的channel事件,使用EventLoop進行無鎖化處理。

unsafe調用原生Nio執行bind(),register(),觸發各種事件 ,更新網絡標識位。

類繼承圖如下

 

NioByteUnsafe用於客戶端,NioMessageUnsafe用於服務端

AbstractUnsafe 分析

舉例一個典型方法register

@Override

public final void register(final ChannelPromise promise) {

if (eventLoop.inEventLoop()) {//根據 eventLoop 屬性判斷 是否 使用 線程池執行

register0(promise);

} else {

try {

eventLoop.execute(new Runnable() {

@Override

public void run() {

register0(promise);

}

});

} catch (Throwable t) {

closeForcibly();// 調用原生 NIO 關閉

closeFuture.setClosed();//設置關閉

promise.setFailure(t);// save Exception

}

}

}

 

private void register0(ChannelPromise promise) {

try {

if (!ensureOpen(promise)) {

return;

}

doRegister();//由子類實現

registered = true;

promise.setSuccess();

pipeline.fireChannelRegistered();//使用pipeline 傳遞 註冊事件

if (isActive()) {

pipeline.fireChannelActive();

}

} catch (Throwable t) {

//...

}

}

}

 

處理邏輯: 先看是否在eventLoop的線程池中執行 ,執行完IO處理,pipeline觸發Registered事件.

而 bind,disconnect,close,write 不需要線程控制和觸發事件參與。

具體的IO 處理都由子類實現。

 

public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {

if (!ensureOpen(promise)) {

return;

}

 

boolean wasActive = isActive();

try {

doBind(localAddress);//調用原生NIO API 執行bind

} catch (Throwable t) {

promise.setFailure(t);

closeIfClosed();

return;

}

if (!wasActive && isActive()) {

invokeLater(new Runnable() { //提交任務執行bind()

@Override

public void run() {//

pipeline.fireChannelActive();

}

});

}

promise.setSuccess();

}

bind 實現比較簡單 沒有EventLoop介入。

doBind有2個實現

NioServerSocketChannel-doBind

 

protected void doBind(SocketAddress localAddress) throws Exception {

javaChannel().socket().bind(localAddress, config.getBacklog());

}

NioSocketChannel-doBind

protected void doBind(SocketAddress localAddress) throws Exception {

javaChannel().socket().bind(localAddress);

}

NioSocketChannel是客戶端,這個bind沒有啥用。

protected void doDeregister() throws Exception {

//取消當前eventLoop中SelectionKey的所有註冊的channel。

eventLoop().cancel(selectionKey());

}

public void beginRead() {

if (!isActive()) {

return;

}

 

try {

doBeginRead();

} catch (final Exception e) {

//...

}

}

doBeginRead由AbstractNioChannel實現 用於修改網絡標識位,NioServerSocketChannel 修改成SelectionKey.OP_ACCEPT,NioSocketChannel 修改成SelectionKey.OP_READ

write和flush方法在寫數據有詳細分析。

小結:AbstractUnsafe 定義了原生NIO,EventLoop,Pipeline協作處理IO 的框架的多個方法,是Netty IO的操作的模板類。

AbstractNioUnsafe分析

public interface NioUnsafe extends Unsafe {

/** underlying :底層的,可理解爲原生NIO的SelectableChannel

* Return underlying {@link SelectableChannel} 返回底層的SelectableChannel

*/

SelectableChannel ch();//獲取原生channel

/**

* Finish connect 完成連接

*/

void finishConnect();

/**

* Read from underlying {@link SelectableChannel} //從原生NIO的SelectableChannel讀取數據

*/

void read();

void forceFlush();//調用flush0

}

Unsafe 中沒定義 read, NioUnsafe 定義了 read 不知道爲啥這樣搞

AbstractNioUnsafe

實現了 返回原生SelectableChannel的方法

實現了connect的框架

 

先分析一下 doConnect方法

@Override

protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {

if (localAddress != null) {

javaChannel().socket().bind(localAddress);

}

 

boolean success = false;

try {

boolean connected = javaChannel().connect(remoteAddress);

if (!connected) {

selectionKey().interestOps(SelectionKey.OP_CONNECT);//line A

}

success = true;

return connected;

} finally {

if (!success) {

doClose();

}

}

}

當SocketChannel設置非阻塞時 connect()不會等待服務端應答,會立即返回false, 當服務應答或連接被拒絕(服務端掛了,或網絡不通)會觸發 OP_CONNECT事件。netty 將SocketChannel默認設置成非阻塞。

OP_CONNECT事件觸發執行finishConnect() ;

服務端接受連接後,客戶端需要執行finishConnect()。

@Override

public 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();

assert connectPromise != null;

 

try {

boolean wasActive = isActive();

doFinishConnect();

fulfillConnectPromise(connectPromise, wasActive);

} catch (Throwable t) {

if (t instanceof ConnectException) {

Throwable newT = new ConnectException(t.getMessage() + ": " + requestedRemoteAddress);

newT.setStackTrace(t.getStackTrace());

t = newT;

}

 

// Use tryFailure() instead of setFailure() to avoid the race against cancel().

connectPromise.tryFailure(t);

closeIfClosed();

} 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);

}

connectPromise = null;

}

}

處理邏輯:驗證連接是否成功,如果失敗 記錄異常信息,嘗試執行channel關閉。無論成功失敗都執行取消定時任務,將connectPromise賦值爲null。

 

connect是AbstractNioUnsafe中最重要的方法,用於客戶端連接服務端。

@Override

public void connect(

final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {

if (!ensureOpen(promise)) {// 校驗SocketChannel isOpen

return;

}

 

try {

if (connectPromise != null) {//校驗 connection是否已經執行

throw new IllegalStateException("connection attempt already made");

}

 

boolean wasActive = isActive();//記錄活動狀態

if (doConnect(remoteAddress, localAddress)) {//NioSocketChannel實現,使用原生NIO連接服務端

fulfillConnectPromise(promise, wasActive);//填充執行結果

} else {//還未成功連接 // line 1

connectPromise = promise;

requestedRemoteAddress = remoteAddress;

 

// Schedule connect timeout.

int connectTimeoutMillis = config().getConnectTimeoutMillis();//默認值30000

if (connectTimeoutMillis > 0) {//添加一個延遲任務,connectTimeoutMillis 毫秒後只執行1次

connectTimeoutFuture = eventLoop().schedule(new Runnable() {{// line 2

@Override//這個任務處理連接超時,記錄異常, 執行關閉 將channel從selector上卸載。

public void run() {

ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;

ConnectTimeoutException cause =

new ConnectTimeoutException("connection timed out: " + remoteAddress);

if (connectPromise != null && connectPromise.tryFailure(cause))

close(voidPromise()); //line 3

}

}

}, connectTimeoutMillis, TimeUnit.MILLISECONDS);

}

promise.addListener(new ChannelFutureListener() {// line 4

@Override

public void operationComplete(ChannelFuture future) throws Exception {

if (future.isCancelled()) {// line5

if (connectTimeoutFuture != null) {

connectTimeoutFuture.cancel(false);

}

connectPromise = null;

close(voidPromise());

}

}

});

}

} catch (Throwable t) {

if (t instanceof ConnectException) {

Throwable newT = new ConnectException(t.getMessage() + ": " + remoteAddress);

newT.setStackTrace(t.getStackTrace());

t = newT;

}

promise.tryFailure(t);

closeIfClosed();

}

}

line 2 添加一個延遲任務處理連接超時,簡稱 "任務A" ,它用於處理連接超時,記錄異常, 執行關閉 將channel從selector上卸載。

line 3 執行關閉 將channel從selector上卸載。

結合 doConnect() 的分析,通常會進入line1。如果超時時間大於0,添加任務A。注意當指定完doConnect() 後,當服務應答或連接被拒絕會觸發事件調用finishConnect(),只要執行了finishConnect()後續都不會觸發定時任務。推測當服務器長時間沒有應答纔有可能觸發任務A。

line4 添加一個listener 執行connectPromise.tryFailure(cause) 後被觸發。

line5 代碼塊內 取消定時任務,記錄異常,執行關閉 將channel從selector上卸載。

future 類型是 DefaultChannelPromise future.isCancelled()==true 什麼時候?

 

DefaultPromise

private static final CauseHolder CANCELLATION_CAUSE_HOLDER = new CauseHolder(new CancellationException());

public boolean cancel(boolean mayInterruptIfRunning) {

Object result = this.result;

if (isDone0(result) || result == UNCANCELLABLE) {

return false;// 操作執行執行完成 (result==SUCCESS) 或 不能取消

}

 

synchronized (this) {

// Allow only once.

result = this.result;

if (isDone0(result) || result == UNCANCELLABLE) {

return false;再次檢查

}

 

this.result = CANCELLATION_CAUSE_HOLDER;//設置 被取消

if (hasWaiters()) {

notifyAll();//解除阻塞

}

}

 

notifyListeners();//觸發監聽器

return true;

}

DefaultPromise 繼承了Future,實現了其 cancel(boolean b)方法,也就是DefaultPromise和它的子類調用了cancel(boolean b)方法,future.isCancelled()==true 才能成立

綜上所述:當 promise執行取消後,line5 代碼塊執行了收尾工作,也就是說 promise.addListener() 是爲了執行收尾工作。

 

填充連接用到的ChannelPromise

private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {

// trySuccess() will return false if a user cancelled the connection attempt.

//trySuccess() 將返回false 如果用戶試圖取消連接。

boolean promiseSet = promise.trySuccess();//給promise填充 執行成功

 

// Regardless if the connection attempt was cancelled, channelActive() event should be triggered,不管連接是否被取消,channelActive事件讀應該被觸發

// because what happened is what happened. 因爲 已經發生的就是已經發生的

if (!wasActive && isActive()) {//成功的 connect ,觸發 ChannelActive事件。

pipeline().fireChannelActive();

}

 

// If a user cancelled the connection attempt, close the channel, which is followed by channelInactive().

if (!promiseSet) {//用戶取消連接,執行關閉SocketChannel處理。

close(voidPromise());

}

}

// 真正寫數據的地方將緩存好的數據

@Override

protected 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();

}

 

 

@Override

public void forceFlush() {

// directly call super.flush0() to force a flush now

super.flush0();

}

forceFlush 依賴 super.flush0(),在寫事件觸發時調用。

flush系列的詳細分析請看 《寫數據》篇

//檢查是否是寫事件觸發的flush

private boolean isFlushPending() {

SelectionKey selectionKey = selectionKey();

return selectionKey.isValid() && (selectionKey.interestOps() & SelectionKey.OP_WRITE) != 0;

}

小結:AbstractNioUnsafe實現了 客戶端連接系列方法,重寫flush,如果是事件觸發則不執行。

NioByteUnsafe分析

重要的方法 只有一個 read()

讀數據分析。

@Override

public void read() {

final ChannelConfig config = config();

final ChannelPipeline pipeline = pipeline();

final ByteBufAllocator allocator = config.getAllocator();//默認UnpooledByteBufAllocator

final int maxMessagesPerRead = config.getMaxMessagesPerRead();//默認 16

RecvByteBufAllocator.Handle allocHandle = this.allocHandle;

if (allocHandle == null) {

this.allocHandle = allocHandle = config.getRecvByteBufAllocator().newHandle();

}

if (!config.isAutoRead()) {

removeReadOp();

}

 

ByteBuf byteBuf = null;

int messages = 0;

boolean close = false;

try {

//allocHandle type is AdaptiveRecvByteBufAllocator$HandleImpl 用於容量預估

int byteBufCapacity = allocHandle.guess();////容量預估

int totalReadAmount = 0;//統計讀取的總字節數,用於容量預估的根據。

do {

byteBuf = allocator.ioBuffer(byteBufCapacity);//創建ByteBuf type is UnpooledUnsafeDirectByteBuf

int writable = byteBuf.writableBytes();

//會出現一次只能讀取部分數據的情況麼??問題A

int localReadAmount = doReadBytes(byteBuf);

if (localReadAmount <= 0) {//沒讀到數據或channel關閉

// not was read release the buffer

byteBuf.release();

close = localReadAmount < 0;//channel 關閉 localReadAmount ==-1

break;

}

 

pipeline.fireChannelRead(byteBuf);//觸發channelRead

byteBuf = null;

 

if (totalReadAmount >= Integer.MAX_VALUE - localReadAmount) {

// Avoid overflow. 避免 totalReadAmount 數值越界

totalReadAmount = Integer.MAX_VALUE;

break;

}

 

totalReadAmount += localReadAmount;

if (localReadAmount < writable) {//讀取的總數比buf可寫數小,說明可讀的數據已經讀完,跳出循環即可。

// Read less than what the buffer can hold,

// which might mean we drained the recv buffer completely.

break;

}

} while (++ messages < maxMessagesPerRead);//循環超過maxMessagesPerRead次數停止。

 

pipeline.fireChannelReadComplete();//觸發 ChannelReadComplete事件

allocHandle.record(totalReadAmount);//調整容量。

 

if (close) {// 需要關閉 ,執行關閉流程

closeOnRead(pipeline);

close = false;

}

} catch (Throwable t) {//處理異常

handleReadException(pipeline, byteBuf, t, close);

}

}

}

問題A有能讀取到部分數據, 因爲只要select() 一次獲取到數據 就會全部讀取。

 

 

NioServerSocketChannel-doReadBytes

protected int doReadBytes(ByteBuf byteBuf) throws Exception {

return byteBuf.writeBytes(javaChannel(), byteBuf.writableBytes());

}

從SocketChannel中讀取數據到byteBuf中去

 

 

容量分配器AdaptiveRecvByteBufAllocator分析

這段代碼獲取了容量分配器

this.allocHandle = allocHandle = config.getRecvByteBufAllocator().newHandle();

跟蹤代碼

public RecvByteBufAllocator getRecvByteBufAllocator() {

return rcvBufAllocator;

}

DefaultChannelConfig

private volatile RecvByteBufAllocator rcvBufAllocator = DEFAULT_RCVBUF_ALLOCATOR;

private static final RecvByteBufAllocator DEFAULT_RCVBUF_ALLOCATOR = AdaptiveRecvByteBufAllocator.DEFAULT;

AdaptiveRecvByteBufAllocator

public static final AdaptiveRecvByteBufAllocator DEFAULT = new AdaptiveRecvByteBufAllocator();

private AdaptiveRecvByteBufAllocator() {

this(DEFAULT_MINIMUM, DEFAULT_INITIAL, DEFAULT_MAXIMUM);//this(64,1024,65536)

}

 

static {

List<Integer> sizeTable = new ArrayList<Integer>();

for (int i = 16; i < 512; i += 16) {

sizeTable.add(i);

}

 

for (int i = 512; i > 0; i <<= 1) {

sizeTable.add(i);

}

 

SIZE_TABLE = new int[sizeTable.size()];

for (int i = 0; i < SIZE_TABLE.length; i ++) {

SIZE_TABLE[i] = sizeTable.get(i);

}

}

SIZE_TABLE數據:[16,32,48,64,80,96,112,128,144,160,176,192,208,224,240,256,272,288,304,320,336,352,368,384,400,416,432,448,464,480,496,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864,134217728,268435456,536870912,1073741824]

SIZE_TABLE 是一個容量預估的數組,每個元素都是一個字節數, 512 之前 從16開始,每次遞增16,512後每次 2倍。

public AdaptiveRecvByteBufAllocator(int minimum, int initial, int maximum) {

//...

int minIndex = getSizeTableIndex(minimum);//二分查找獲取minimum的位置

if (SIZE_TABLE[minIndex] < minimum) {//調整預估值,使預估值一定大於等於minimum

this.minIndex = minIndex + 1;

} else {

this.minIndex = minIndex;

}//minIndex 最後等於3

 

int maxIndex = getSizeTableIndex(maximum);

if (SIZE_TABLE[maxIndex] > maximum) {

this.maxIndex = maxIndex - 1;

} else {

this.maxIndex = maxIndex;//

}//minIndex 最後等於38

 

this.initial = initial;//1024

}

其內部類創建

public Handle newHandle() {

return new HandleImpl(minIndex, maxIndex, initial);// minIndex==3,maxIndex==38,initial==1024

}

HandleImpl(int minIndex, int maxIndex, int initial) {

this.minIndex = minIndex;

this.maxIndex = maxIndex;

 

index = getSizeTableIndex(initial);//二分查找獲取initial的index

nextReceiveBufferSize = SIZE_TABLE[index];

}

@Override

public void record(int actualReadBytes) {

//實際讀取bytes數小於等於較小的容量數進縮容

if (actualReadBytes <= SIZE_TABLE[Math.max(0, index - INDEX_DECREMENT - 1)]) {

if (decreaseNow) {//是否立即縮容

index = Math.max(index - INDEX_DECREMENT, minIndex);

nextReceiveBufferSize = SIZE_TABLE[index];

decreaseNow = false;

} else {

decreaseNow = true;

}

//實際讀取bytes數大於等於當前的容量數進行擴容

} else if (actualReadBytes >= nextReceiveBufferSize) {

 

index = Math.min(index + INDEX_INCREMENT, maxIndex);

nextReceiveBufferSize = SIZE_TABLE[index];

decreaseNow = false;

}

}

}

record實現了數組的容量調整

小結:AdaptiveRecvByteBufAllocator通過容量增長經驗定義一個 容量預估的SIZE_TABLE ,實現了 容量預估,和容量調整的功能。

NioMessageUnsafe分析

服務端用到的Unsafe

private final List<Object> readBuf = new ArrayList<Object>();

NioMessageUnsafe中最重要的就是read方法了,這個方法用於初始化NioSocketChannel。

詳情參照《NioSocketChanel初始化》篇

下面總結一下NioByteUnsafe和NioMessageUnsafe的區別

它們主要是read方法的區別,下面根據功能,使用頻率,重要度區分。

NioByteUnsafe:read方法讀取channel數據 常用 重要

NioMessageUnsafe:read方法初始化 NioSocketChannel 常用 重要

write方法也很重要,但不是NioByteUnsafe和NioMessageUnsafe的主要實現,詳細見《寫數據》篇。

總結:本文分析了Unsafe體系的各個類以及其重要的方法,AbstractUnsafe實現IO操作處理的框架,AbstractNioUnsafe實現了客戶端連接系列方法強制刷新系列方法,NioByteUnsafe實現客戶端讀取數據的read方法,NioMessageUnsafe實現了 初始化 NioSocketChanne的read方法。

 

 

 

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