每個 ChannelSocket 的 Unsafe 都有一個綁定的 ChannelOutboundBuffer , Netty 向站外輸出數據的過程統一通過 ChannelOutboundBuffer 類進行封裝,目的是爲了提高網絡的吞吐量,在外面調用 write 的時候,數據並沒有寫到 Socket,而是寫到了 ChannelOutboundBuffer 這裏,當調用 flush 的時候,才真正的向 Socket 寫出。
ChannelOutboundBuffer 類屬性
public final class ChannelOutboundBuffer {
// 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);
private final Channel channel;
// Entry(flushedEntry) --> ... Entry(unflushedEntry) --> ... Entry(tailEntry)
//
// 緩存鏈表中被刷新的第一個元素
private Entry flushedEntry;
// 緩存鏈表中中第一個未刷新的元素
private Entry unflushedEntry;
// 緩存鏈表中的尾元素
private Entry tailEntry;
// 刷新但還沒有寫入到 socket 中的數量
private int flushed;
從類的屬性中可以看出 ChannelOutboundBuffer 定義了一個鏈表進行存儲寫入的數據。
1、ChannelOutboundBuffer 添加了N個 Entry
2、當 flush 後的效果
3、flush 後把所有的數據都寫入 Socket 中
4、當再次寫入 Entry 後
下面針對操作這些操作進行分析,添加、刷新、刪除等操作。
addMessage() 添加操作
public void addMessage(Object msg, int size, ChannelPromise promise) {
Entry entry = Entry.newInstance(msg, size, total(msg), promise);
if (tailEntry == null) {
flushedEntry = null;
} 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);
}
1、創建一個 新的Entry。
2、判斷 tailEntry 是否爲 null,如果爲 null 說明鏈表爲空。則把 flushedEntry 置爲null。
3、如果 tailEntry 不爲空,則把新添加的 Entry 添加到 tailEntry 後面 。
4、 將新添加的 Entry 設置爲 鏈表的 tailEntry。
5、如果 unflushedEntry 爲null,說明沒有未被刷新的元素。新添加的Entry 肯定是未被刷新的,則把當前 Entry 設置爲 unflushedEntry 。
6、統計未被刷新的元素的總大小。
addFlush() 刷新操作
當 addMessage 成功添加進 ChannelOutboundBuffer 後,就需要 flush 刷新到 Socket 中去。但是這個方法並不是做刷新到 Socket 的操作。而是將 unflushedEntry 的引用轉移到 flushedEntry 引用中,表示即將刷新這個 flushedEntry,至於爲什麼這麼做?
答:因爲 Netty 提供了 promise,這個對象可以做取消操作,例如,不發送這個 ByteBuf 了,所以,在 write 之後,flush 之前需要告訴 promise 不能做取消操作了。
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;
}
}
1、通過 unflushedEntry 獲取未被刷新元素 entry。
2、如果 entry 爲null 說明沒有待刷新的元素,不執行任何操作
3、如果 entry 不爲 null,說明有需要被刷新的元素
4、如果 flushedEntry == null 說明當前沒有正在刷新的任務,則把 entry 設置爲 flushedEntry 刷新的起點。
5、循環設置 entry, 設置這些 entry 狀態設置爲非取消狀態,如果設置失敗,則把這些entry 節點取消並使 totalPendingSize 減去這個節點的字節大小。
在調用完 outboundBuffer.addFlush() 方法後,Channel 會調用 flush0 方法做真正的刷新。代碼參見 AbstractUnsafe.flush() 方法。
remove() 刪除操作
public boolean remove() {
Entry e = flushedEntry;
if (e == null) {
clearNioBuffers();
return false;
}
Object msg = e.msg;
ChannelPromise promise = e.promise;
int size = e.pendingSize;
removeEntry(e);
if (!e.cancelled) {
// only release message, notify and decrement if it was not canceled before.
ReferenceCountUtil.safeRelease(msg);
safeSuccess(promise);
decrementPendingOutboundBytes(size, false, true);
}
// recycle the entry
e.recycle();
return true;
}
1、獲取 flushedEntry 節點,鏈表的頭結點。如果獲取不到清空 ByteBuf 緩存。
2、在鏈表上移除該 Entry。如果之前沒有取消,只釋放消息、通知和遞減。
3、回收 Entry 對象。
removeEntry() 鏈表移除節點
private void removeEntry(Entry e) {
if (-- flushed == 0) {
// processed everything
flushedEntry = null;
if (e == tailEntry) {
tailEntry = null;
unflushedEntry = null;
}
} else {
flushedEntry = e.next;
}
}
1、如果 flushed ==0 說明,鏈表中所有 flush 的數據都已經發送到 Socket 中。把 flushedEntry 置位 null。此時鏈表可能還有 unflushedEntry 數據。
如果此時 e == tailEntry 說明鏈表爲空,則把 tailEntry 和 unflushedEntry 都置爲空。
flush==0,可能是這種狀態,如下圖:
2、把 flushedEntry 置爲下一個節點(flushedEntry 此時是頭結點)。
Entry 對象池使用
由於 Entry 使用比較頻繁,會頻繁的創建和銷燬,這裏使用了 Entry 的對象池,創建的時候從緩存中獲取,銷燬時回收。
下面看下 Entry 的創建過程
Entry 創建
public void addMessage(Object msg, int size, ChannelPromise promise) {
Entry entry = Entry.newInstance(msg, size, total(msg), promise);
.....
使用 Entry.newInstance 方法進行創建 Entry 對象。
static final class Entry {
private static final Recycler<Entry> RECYCLER = new Recycler<Entry>() {
@Override
protected Entry newObject(Handle<Entry> handle) {
return new Entry(handle);
}
};
static Entry newInstance(Object msg, int size, long total, ChannelPromise promise) {
Entry entry = RECYCLER.get();
entry.msg = msg;
entry.pendingSize = size + CHANNEL_OUTBOUND_BUFFER_ENTRY_OVERHEAD;
entry.total = total;
entry.promise = promise;
return entry;
}
Entry.newInstance() 方法收下從 Recycler 中獲取 Entry 對象,如果獲取不到則使用 Recycler.newObject() 方法創建一個 Entry 對象,調用 newObject() 方法在 Recycler.get() 方法中,代碼如下:
public abstract class Recycler<T> {
public final T get() {
if (maxCapacityPerThread == 0) {
return newObject((Handle<T>) NOOP_HANDLE);
}
Stack<T> stack = threadLocal.get();
DefaultHandle<T> handle = stack.pop();
if (handle == null) {
handle = stack.newHandle();
handle.value = newObject(handle);
}
return (T) handle.value;
}
從 Recycler 中可以看出,Entry 對象是存儲在 Stack 中。如果 Stack 中沒有可用的 Stack,則調用 newObject() 方法創建。
Entry 對象回收
public boolean remove() {
Entry e = flushedEntry;
...
e.recycle();
return true;
}
當 Entry 從鏈表中移除的時候回調用 e.recycle() 方法。
static final class Entry {
void recycle() {
next = null;
bufs = null;
buf = null;
msg = null;
promise = null;
progress = 0;
total = 0;
pendingSize = 0;
count = -1;
cancelled = false;
handle.recycle(this);
}
Entry.recycle() 方法會把 Entry 中成員變量全部初始化。然後在調用 handle.recycle() 方法。
public abstract class Recycler<T> {
static final class DefaultHandle<T> implements Handle<T> {
@Override
public void recycle(Object object) {
if (object != value) {
throw new IllegalArgumentException("object does not belong to handle");
}
Stack<?> stack = this.stack;
if (lastRecycledId != recycleId || stack == null) {
throw new IllegalStateException("recycled already");
}
stack.push(this);
}
}
Recycler.recycle() 方法又把 Entry 對象壓進 stack 中。