1、ChannelHandler源碼解析
1.1、ChannelHandler功能說明
ChannelHandler類似servlet的Filter過濾器,其負責對I/O事件或I/O操作進行攔截和處理,可以選擇性地攔截和處理自己感興趣的事件,也可以透傳和終止事件傳播。其實現原理是基于pipeline構成事件處理責任鏈,inbound或outbound事件沿著處理責任鏈中的ChannelHandler傳播處理。
基于ChannelHandler接口,可以方便實現業務邏輯的個性定制,如打印日志、統一異常封裝、性能統計等。
ChannelHandler支持如下注解:
- @Sharable:表示多個ChannelPipeline可以共享同一個ChannelHandler,否則不允許一個ChannelHandler添加到多個Pipeline中。
- @Skip:被Skip注解的方法不會被調用,直接被忽略。
1.2、Channel的生命周期
Channel有以下狀態模型:
- channelUnregistered:Channel已創建,但還未注冊的EventLoop中。
- channelRegistered:Channel已經被注冊到EventLoop中;
- channelActive:Channel處于活動狀態,即其已經連接到遠程節點,可以進行數據收發處理;
- channelInactive:Channel還未連接帶遠程節點;
Channel各狀態轉換圖如下:
你會看到多個channelRegistered和channelUnregistered狀態的變化,而永遠只有一個channelActive和channelInactive的狀態,因為一個通道在其生命周期內只能連接一次,之后就會被回收;重新連接,則是創建一個新的通道。
1.3、ChannelHandler類繼承圖
1.4、ChannelHandler接口
此接口定義了ChannelHandler在pipeline中的生命周期操作。在ChannelHandler被添加到ChannelPipeline中或從ChannelPipeline中移除是會調用這些操作。
操作方法如下:
- handlerAdded:當把ChannelHandler 添加到ChannelPipeline 中時被調用,只有調用此方法后,I/O事件的傳播才會經由此ChannelHandler處理,否則直接跳過此ChannelHandler;
- handlerRemoved:當從ChannelPipeline 中移除ChannelHandler 時被調用,當被移除后,Channel事件傳播將不會經由此ChannelHandler;
- exceptionCaught:當處理過程中在ChannelPipeline 中有錯誤產生時被調用;
1.5、ChannelInboundHandler接口
此接口定義了Channel的I/O事件接口,這些接口與Channel的生命周期密切相關。
方法名稱 | 功能說明 |
---|---|
channelRegistered | 當Channel 已經注冊到它的EventLoop 并且能夠處理I/O 時被調用 |
channelUnregistered | 當Channel 從它的EventLoop 注銷并且無法處理任何I/O 時被調用 |
channelActive | 當Channel 處于活動狀態時被調用;Channel 已經連接/綁定并且已經就緒 |
channelInactive | 當Channel 離開活動狀態并且不再連接它的遠程節點時被調用 |
channelReadComplete | 當Channel上的一個讀操作完成時被調用 |
channelRead | 當從Channel 讀取數據時被調用 |
ChannelWritabilityChanged | 當Channel 的可寫狀態發生改變時被調用。用戶可以確保寫操作不會完成得太快(以避免發生OutOfMemoryError)或者可以在Channel 變為再次可寫時恢復寫入。可以通過調用Channel 的isWritable()方法來檢測Channel 的可寫性。與可寫性相關的閾值可以通過Channel.config().setWriteHighWaterMark()和Channel.config().setWriteLowWaterMark()方法來設置 |
userEventTriggered | 當ChannelnboundHandler.fireUserEventTriggered()方法被調用時被調用,因為一個POJO 被傳經了ChannelPipeline |
對于channelRead()方法,當某個子類重寫此方法時,其負責顯示地釋放與池化ByteBuf實例相關的內存。Netty為此提供了ReferenceCountUtil.release()工具方法進行內存釋放。同時,Netty也提供了SimpleChannelInboundHandler,其會自動釋放ButBuf內存。
實現源碼如下:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
if (acceptInboundMessage(msg)) {
@SuppressWarnings("unchecked")
I imsg = (I) msg;
channelRead0(ctx, imsg);
} else {
release = false;
ctx.fireChannelRead(msg);
}
} finally {
if (autoRelease && release) {
ReferenceCountUtil.release(msg);
}
}
}
1.6、ChannelOutboundHandler接口
出站操作和數據將由ChannelOutboundHandler接口處理。其方法將被Channel、ChannelPipeline及ChannelHandlerContext調用。
方法名稱 | 功能說明 |
---|---|
bind(ChannelHandlerContext,SocketAddress,ChannelPromise) | 當請求將Channel 綁定到本地地址時被調用 |
connect(ChannelHandlerContext,SocketAddress,SocketAddress,ChannelPromise) | 當請求將Channel 連接到遠程節點時被調用 |
disconnect(ChannelHandlerContext,ChannelPromise) | 當請求將Channel 從遠程節點斷開時被調用 |
close(ChannelHandlerContext,ChannelPromise) | 當請求關閉Channel 時被調用 |
deregister(ChannelHandlerContext,ChannelPromise) | 當請求將Channel 從它的EventLoop 注銷時被調用 |
read(ChannelHandlerContext) | 當請求從Channel 讀取更多的數據時被調 |
flush(ChannelHandlerContext) | 當請求通過Channel 將入隊數據沖刷到遠程節點時被調用 |
write(ChannelHandlerContext,Object,ChannelPromise) | 當請求通過Channel 將數據寫到遠程節點時被調用 |
ChannelPromise與ChannelFuture:ChannelOutboundHandler中的大部分方法都需要一個ChannelPromise參數,以便在操作完成時得到通知。ChannelPromise是ChannelFuture的一個子類,其定義了一些可寫的方法,如setSuccess()和setFailure(),從而使ChannelFuture不可變。
1.7、ChannelHandlerAdapter抽象類
ChannelHandlerAdapter是ChannelHandler的抽象實現類,其主要實現了isSharable()方法,此方法判斷ChannelHandler是否為多ChannelPipeline共享的。
1.8、ChannelInboundHandlerAdapter類
ChannelInboundHandlerAdapter為ChannelInboundHandler的實現類,其實現非常簡單,所有的方法都是調用其對應的ChannelHandlerContext對應的fileXXX進行處理。
源碼示例如下:
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelRegistered();
}
1.9、ChannelOutboundHandlerAdapter類
ChannelOutboundHandlerAdapter為ChannelOutboundHandler的實現類,其實現非常簡單,直接調用對應的ChannelHandlerContext對應方法。
源碼示例如下:
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
ChannelPromise promise) throws Exception {
ctx.bind(localAddress, promise);
}
2、Decoder(解碼器)
2.1、粘包、拆包
粘包、拆包是Socket編程中最常見的問題,TCP是個流協議,TCP底層并不了解上層業務的具體含義,其會根據TCP緩沖區的實際情況進行包的劃分。
在所以業務上:
- 一個完整的包可能會被TCP拆分為多個包進行發送(拆包);
- 多個小的包也有可能被封裝成一個大的包進行發送(粘包);
2.1.1、粘包、拆包產生原因
產生原因大致有以下三種:
- 應用程序寫入的字節大小大于Socket發送緩沖區大小
- 進行MSS大小的TCP,MSS是最大報文段長度的縮寫,是TCP報文段中的數據字段最大長度,MSS=TCP報文段長度-TCP首部長度
- 以太網的Payload大于MTU,進行IP分片,MTU是最大傳輸單元的縮寫,以太網的MTU為1500字節
2.1.1、粘包、拆包解決思路
由于底層的TCP無法理解上層的業務數據,所以在底層是無法保證數據包不被拆分和重組的,這個問題只能通過上層的應用協議棧設計來解決,根據業界的主流協議的解決方案,可以歸納如下:
- 消息定長,例如每個報文的大小固定為200字節,如果不夠空位補空格
- 包尾增加回車換行符進行分割,例如FTP協議
- 將消息分為消息頭和消息體,消息頭中包含表示長度的字段,通常涉及思路為消息頭的第一個字段使用int32來表示消息的總長度
- 更復雜的應用層協議
拆包思路:
當數據滿足了 解碼條件時,將其拆開。放到數組。然后發送到業務 handler 處理。
半包思路:
當讀取的數據不夠時,先存起來,直到滿足解碼條件后,放進數組。送到業務 handler 處理。
2.2、ByteToMessageDecoder
ByteToMessageDecoder解碼器主要功能為將ByteBuf緩沖區中的數據轉換為對象,用戶的解碼器基礎ByteToMessageDecoder,只需實現decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)抽象方法即可完成將ByteBuf到POJO對象的解碼。
由于ByteToMessageDecoder并未考慮TCP粘包和組包等場景,讀半包需要用戶解碼器自己負責處理。因此,大多數場景不會直接繼承ByteToMessageDecoder進行數據解碼處理,而是繼承一些更高級的解碼器來屏蔽半包的處理。
2.2.1、成員變量
ByteToMessageDecoder成員變量如下:
private static final byte STATE_INIT = 0;
private static final byte STATE_CALLING_CHILD_DECODE = 1;
private static final byte STATE_HANDLER_REMOVED_PENDING = 2;
ByteBuf cumulation;
private Cumulator cumulator = MERGE_CUMULATOR;
private boolean singleDecode;
private boolean decodeWasNull;
private boolean first;
/**
* A bitmask where the bits are defined as
* <ul>
* <li>{@link #STATE_INIT}</li>
* <li>{@link #STATE_CALLING_CHILD_DECODE}</li>
* <li>{@link #STATE_HANDLER_REMOVED_PENDING}</li>
* </ul>
*/
private byte decodeState = STATE_INIT;
private int discardAfterReads = 16;
private int numReads;
成員變量說明:
cumulation:累計器緩存
cumulator:累計器,對從ByteBuf讀取的數據進行累積處理;
singleDecode:是否為單消息解碼器;
decodeWasNull:解碼的數據為空;
first:第一次讀取數據;
decodeState :解碼器狀態;
discardAfterReads :讀取多少次后對累計器緩沖區進行discardSomeReadBytes()處理。
numReads:記錄讀取次數;
2.2.2、累計器解讀
成員變量cumulator為ByteToMessageDecoder的累計器,其有兩種實現:
MERGE_CUMULATOR:合并累計器
public static final Cumulator MERGE_CUMULATOR = new Cumulator() {
@Override
public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
final ByteBuf buffer;
if (cumulation.writerIndex() > cumulation.maxCapacity() - in.readableBytes()
|| cumulation.refCnt() > 1 || cumulation.isReadOnly()) {
// Expand cumulation (by replace it) when either there is not more room in the buffer
// or if the refCnt is greater then 1 which may happen when the user use slice().retain() or
// duplicate().retain() or if its read-only.
//
// See:
// - https://github.com/netty/netty/issues/2327
// - https://github.com/netty/netty/issues/1764
buffer = expandCumulation(alloc, cumulation, in.readableBytes());
} else {
buffer = cumulation;
}
buffer.writeBytes(in);
in.release();
return buffer;
}
};
從cumulate方法可知,其主要是將 unsafe.read 傳遞過來的 ByteBuf 的內容寫入到 cumulation 累積區中,然后釋放掉舊的內容,由于這個變量是成員變量,因此可以多次調用 channelRead 方法寫入。同時這個方法也考慮到了擴容的問題,當容量 不夠時會先進行擴容處理。
COMPOSITE_CUMULATOR:混合累計器
/**
* Cumulate {@link ByteBuf}s by add them to a {@link CompositeByteBuf} and so do no memory copy whenever possible.
* Be aware that {@link CompositeByteBuf} use a more complex indexing implementation so depending on your use-case
* and the decoder implementation this may be slower then just use the {@link #MERGE_CUMULATOR}.
*/
public static final Cumulator COMPOSITE_CUMULATOR = new Cumulator() {
@Override
public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
ByteBuf buffer;
if (cumulation.refCnt() > 1) {
// Expand cumulation (by replace it) when the refCnt is greater then 1 which may happen when the user
// use slice().retain() or duplicate().retain().
//
// See:
// - https://github.com/netty/netty/issues/2327
// - https://github.com/netty/netty/issues/1764
buffer = expandCumulation(alloc, cumulation, in.readableBytes());
buffer.writeBytes(in);
in.release();
} else {
CompositeByteBuf composite;
if (cumulation instanceof CompositeByteBuf) {
composite = (CompositeByteBuf) cumulation;
} else {
composite = alloc.compositeBuffer(Integer.MAX_VALUE);
composite.addComponent(true, cumulation);
}
composite.addComponent(true, in);
buffer = composite;
}
return buffer;
}
};
由以上源碼可知,此累計器利用CompositeByteBuf類型的ByteBuf,將讀取到的數據直接作為一個Component放到CompositeByteBuf中,相較于拷貝,此種累計器性能會更好,但同時也更復雜。
2.2.3、讀取數據處理
channelRead()實現源碼:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
CodecOutputList out = CodecOutputList.newInstance();
try {
ByteBuf data = (ByteBuf) msg;
first = cumulation == null;
if (first) {
cumulation = data;
} else {
cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
}
callDecode(ctx, cumulation, out);
} catch (DecoderException e) {
throw e;
} catch (Exception e) {
throw new DecoderException(e);
} finally {
if (cumulation != null && !cumulation.isReadable()) {
numReads = 0;
cumulation.release();
cumulation = null;
} else if (++ numReads >= callDecode) {
// We did enough reads already try to discard some bytes so we not risk to see a OOME.
// See https://github.com/netty/netty/issues/4275
numReads = 0;
discardSomeReadBytes();
}
int size = out.size();
decodeWasNull = !out.insertSinceRecycled();
fireChannelRead(ctx, out, size);
out.recycle();
}
} else {
ctx.fireChannelRead(msg);
}
}
static void fireChannelRead(ChannelHandlerContext ctx, CodecOutputList msgs, int numElements) {
for (int i = 0; i < numElements; i ++) {
ctx.fireChannelRead(msgs.getUnsafe(i));
}
}
主要處理流程:
- 從對象池中取出一個空的數組。
- 判斷成員變量是否是第一次使用,(注意,既然使用了成員變量,所以這個 handler 不能是 handler 的。)將 unsafe 中傳遞來的數據寫入到這個 cumulation 累積區中。
- 寫到累積區后,調用子類的 callDecode方法,嘗試將累積區的內容解碼。
- 解碼完成后,若累計器無可讀的數據,則清除讀計數和累計器緩沖區,如果讀次數大于callDecode(默認為16次),則調用discardSomeReadBytes對累計器的緩沖區進行壓縮處理。
- 如果解碼的對象為空,則設置decodeWasNull 為true;
- 調用fireChannelRead()將解碼的對象list遍歷交由fireChannelRead()進行處理。
- 將數組清空。并還給對象池。
2.2.3、解碼處理
解碼實現源碼:
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
try {
while (in.isReadable()) {
int outSize = out.size();
if (outSize > 0) {
fireChannelRead(ctx, out, outSize);
out.clear();
// Check if this handler was removed before continuing with decoding.
// If it was removed, it is not safe to continue to operate on the buffer.
//
// See:
// - https://github.com/netty/netty/issues/4635
if (ctx.isRemoved()) {
break;
}
outSize = 0;
}
int oldInputLength = in.readableBytes();
decodeRemovalReentryProtection(ctx, in, out);
// Check if this handler was removed before continuing the loop.
// If it was removed, it is not safe to continue to operate on the buffer.
//
// See https://github.com/netty/netty/issues/1664
if (ctx.isRemoved()) {
break;
}
if (outSize == out.size()) {
if (oldInputLength == in.readableBytes()) {
break;
} else {
continue;
}
}
if (oldInputLength == in.readableBytes()) {
throw new DecoderException(
StringUtil.simpleClassName(getClass()) +
".decode() did not read anything but decoded a message.");
}
if (isSingleDecode()) {
break;
}
}
} catch (DecoderException e) {
throw e;
} catch (Exception cause) {
throw new DecoderException(cause);
}
}
final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
throws Exception {
decodeState = STATE_CALLING_CHILD_DECODE;
try {
decode(ctx, in, out);
} finally {
boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING;
decodeState = STATE_INIT;
if (removePending) {
handlerRemoved(ctx);
}
}
}
主要處理流程:
- 循環讀取緩沖區數據,如果有上次解碼成功而未傳播給下個ChannelHandler進行處理的對象,則交由下個Handler進行處理,并清除緩存的對象列表;
- 讀取數據并調用 decodeRemovalReentryProtection 方法,內部調用了子類重寫的 decode 方法,很明顯,這里是個模板模式。decode 方法的邏輯就是將累積區的內容按照約定進行解碼,如果成功解碼,就添加到數組中。同時該方法也會檢查該 handler 的狀態,如果被移除出 pipeline 了,就將累積區的內容直接刷新到后面的 handler 中。
- 如果 Context 節點被移除了,直接結束循環。如果解碼前的數組大小和解碼后的數組大小相等,且累積區的可讀字節數沒有變化,說明此次讀取什么都沒做,就直接結束。如果字節數變化了,說明雖然數組沒有增加,但確實在讀取字節,就再繼續讀取。
- 如果上面的判斷過了,說明數組讀到數據了,但如果累積區的 readIndex 沒有變化,則拋出異常,說明沒有讀取數據,但數組卻增加了,子類的操作是不對的。
- 如果是個單次解碼器,解碼一次就直接結束了。
2.3、MessageToMessageDecoder
MessageToMessageDecoder負責將一個POJO對象解碼為另一個POJO對象。
2.3.1、數據讀取解析
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
CodecOutputList out = CodecOutputList.newInstance();
try {
if (acceptInboundMessage(msg)) {
@SuppressWarnings("unchecked")
I cast = (I) msg;
try {
decode(ctx, cast, out);
} finally {
ReferenceCountUtil.release(cast);
}
} else {
out.add(msg);
}
} catch (DecoderException e) {
throw e;
} catch (Exception e) {
throw new DecoderException(e);
} finally {
int size = out.size();
for (int i = 0; i < size; i ++) {
ctx.fireChannelRead(out.getUnsafe(i));
}
out.recycle();
}
}
主要處理流程:
- 通過RecyclableArrayList創建新的可循環利用的list對象;
- 對解碼的消息類型進行判斷,通過則調用decode()方法進行解碼處理,處理完成釋放緩存對象;
- 類型匹配失敗,則直接將對象放到out的list中;
- 最終遍歷解碼后的POJOlist,將各個解碼對象發給下個Handler處理;
- 對象池釋放;
2.4、LineBasedFrameDecoder
LineBasedFrameDecoder是Netty提供的一種解碼器,專門用于以換行符為分割的消息的解碼,能夠處理\n和\r\n的換行符,其實現了對粘包、半包等的處理。LineBasedFrameDecoder繼承ByteToMessageDecoder,這樣對于轉換字節為POJO對象的底層工作就交給ByteToMessageDecoder類來實現了,LineBasedFrameDecoder類只需要負責對消息的字節流進行解包即可。
處理大體思路:
- 沒有需要忽略的數據:
(1)、若找到換行符,若長度超過最大長度,則直接失敗處理;否則讀取數據,并返回數據;
(2)、若未找到換行符,若長度超過最大長度,則進行需要忽略的數據的相關參數設置,并等待底層繼續讀取數據;
- 有需要忽略的數據:
(1)、若找到換行符,則直接忽略掉上次的半包數據及本次讀取的數據,并設置無忽略相關的參數;
(2)、若未找到換行符。則直接設置忽略相關的參數,等待底層繼續讀取數據;
解碼實現源碼:
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
final int eol = findEndOfLine(buffer);
if (!discarding) {
if (eol >= 0) {
final ByteBuf frame;
final int length = eol - buffer.readerIndex();
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
if (length > maxLength) {
buffer.readerIndex(eol + delimLength);
fail(ctx, length);
return null;
}
if (stripDelimiter) {
frame = buffer.readRetainedSlice(length);
buffer.skipBytes(delimLength);
} else {
frame = buffer.readRetainedSlice(length + delimLength);
}
return frame;
} else {
final int length = buffer.readableBytes();
if (length > maxLength) {
discardedBytes = length;
buffer.readerIndex(buffer.writerIndex());
discarding = true;
offset = 0;
if (failFast) {
fail(ctx, "over " + discardedBytes);
}
}
return null;
}
} else {
if (eol >= 0) {
final int length = discardedBytes + eol - buffer.readerIndex();
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
buffer.readerIndex(eol + delimLength);
discardedBytes = 0;
discarding = false;
if (!failFast) {
fail(ctx, length);
}
} else {
discardedBytes += buffer.readableBytes();
buffer.readerIndex(buffer.writerIndex());
}
return null;
}
}
private int findEndOfLine(final ByteBuf buffer) {
int totalLength = buffer.readableBytes();
int i = buffer.forEachByte(buffer.readerIndex() + offset, totalLength - offset, ByteProcessor.FIND_LF);
if (i >= 0) {
offset = 0;
if (i > 0 && buffer.getByte(i - 1) == '\r') {
i--;
}
} else {
offset = totalLength;
}
return i;
}
主要處理流程:
- 查找行尾,findEndOfLine()中主要查找字符'\n',找到并檢查其其前一個字符是否為’\r‘;
- 如果不能忽略數據且找到換行符,則一行數據進行處理;如果數據長度大于maxLength,則設置讀索引readerIndex并進行異常處理;如果返回的數據需要忽略換行符,則只讀取數據并跳過換行符,否則讀取數據及換行符;
- 如果不能忽略但未找到換行符,若可讀數據長度大于maxLength,則設置discardedBytes為緩沖區可讀數據的長度,設置讀索引readerIndex為寫索引writterIndex,設置discarding為true表示有忽略的數據;偏移量設置為0;若設置了快速失敗則直接報異常;若可讀數據長度小于等于maxLength則不對數據進行處理;
- 當有忽略的數據時,若找到換行符,則設置讀索引readerIndex為換行符之后,設置discardedBytes為0,設置discarding為false,并做快速失敗處理;
- 當有忽略的數據時,若未找到換行符,則這是discardedBytes為可讀字節數并設置讀索引readerIndex為寫索引writterIndex;
2.5、DelimiterBasedFrameDecoder
DelimiterBasedFrameDecoder實現了根據一個或多個分隔符對ByteBuf進行解碼處理;其處理流程大體和LineBasedFrameDecoder類似。
處理大體思路:
- 根據分隔符列表遍歷緩沖區數據,找到匹配某個分隔符的最短的索引位置及其對應的分隔符;
- 分隔符找到,并且有需要忽略的數據,則對數據做忽略及異常處理;否則處理數據并返回數據;
- 分隔符未找到,若無需要忽略的數據,若可讀數據長度大于最大長度,則進行忽略數據的相關處理;
- 分隔符為找到,若喲需要忽略的數據,則直接進行忽略數據的處理。
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
if (lineBasedDecoder != null) {
return lineBasedDecoder.decode(ctx, buffer);
}
// Try all delimiters and choose the delimiter which yields the shortest frame.
int minFrameLength = Integer.MAX_VALUE;
ByteBuf minDelim = null;
for (ByteBuf delim: delimiters) {
int frameLength = indexOf(buffer, delim);
if (frameLength >= 0 && frameLength < minFrameLength) {
minFrameLength = frameLength;
minDelim = delim;
}
}
if (minDelim != null) {
int minDelimLength = minDelim.capacity();
ByteBuf frame;
if (discardingTooLongFrame) {
// We've just finished discarding a very large frame.
// Go back to the initial state.
discardingTooLongFrame = false;
buffer.skipBytes(minFrameLength + minDelimLength);
int tooLongFrameLength = this.tooLongFrameLength;
this.tooLongFrameLength = 0;
if (!failFast) {
fail(tooLongFrameLength);
}
return null;
}
if (minFrameLength > maxFrameLength) {
// Discard read frame.
buffer.skipBytes(minFrameLength + minDelimLength);
fail(minFrameLength);
return null;
}
if (stripDelimiter) {
frame = buffer.readRetainedSlice(minFrameLength);
buffer.skipBytes(minDelimLength);
} else {
frame = buffer.readRetainedSlice(minFrameLength + minDelimLength);
}
return frame;
} else {
if (!discardingTooLongFrame) {
if (buffer.readableBytes() > maxFrameLength) {
// Discard the content of the buffer until a delimiter is found.
tooLongFrameLength = buffer.readableBytes();
buffer.skipBytes(buffer.readableBytes());
discardingTooLongFrame = true;
if (failFast) {
fail(tooLongFrameLength);
}
}
} else {
// Still discarding the buffer since a delimiter is not found.
tooLongFrameLength += buffer.readableBytes();
buffer.skipBytes(buffer.readableBytes());
}
return null;
}
}
處理流程:
- 若分隔符為換行分隔符,則直接調用換行分隔符的解碼器進行處理;
- 遍歷所有分隔符,查找到索引最小的分隔符及其對應的索引;
- 若分隔符找到,若上次有需要忽略的數據,則直接進行數據跳過處理;否則讀取數據并返回數;
- 若未找到分隔符,若無需要忽略的數據,則判斷數據長度是否大于最大長度,是則進行忽略數據處理,否則不進行處理;
2.6、LengthFieldBasedFrameDecoder
LengthFieldBasedFrameDecoder是基于自定義數據長度進行解碼的解碼器;
2.6.1、基本屬性
private final ByteOrder byteOrder;
private final int maxFrameLength;
private final int lengthFieldOffset;
private final int lengthFieldLength;
private final int lengthFieldEndOffset;
private final int lengthAdjustment;
private final int initialBytesToStrip;
private final boolean failFast;
private boolean discardingTooLongFrame;
private long tooLongFrameLength;
private long bytesToDiscard;
byteOrder:字節序,大端或小端;
maxFrameLength:最大數據長度,超過此長度說明數據包錯誤;
lengthFieldOffset:length字段相對于讀索引readerIndex的偏移量;
lengthFieldLength:length字段的字節長度,其長度只能為1、2、3、4、8中的一個,其他長度錯誤;
lengthFieldEndOffset:表示緊跟長度域字段后面的第一個字節的在整個數據包中的偏移量
lengthAdjustment:計算調整后的長度字段的偏移量;
initialBytesToStrip:初始需要跳過的長度;
failFast:快速失敗標志;
discardingTooLongFrame:忽略過長數據包標志;
tooLongFrameLength:過長數據的當前長度;
bytesToDiscard:需要忽略的字節數;
2.6.1、解碼實現流程
decode()實現源碼:
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
if (discardingTooLongFrame) {
discardingTooLongFrame(in);
}
if (in.readableBytes() < lengthFieldEndOffset) {
return null;
}
int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);
if (frameLength < 0) {
failOnNegativeLengthField(in, frameLength, lengthFieldEndOffset);
}
frameLength += lengthAdjustment + lengthFieldEndOffset;
if (frameLength < lengthFieldEndOffset) {
failOnFrameLengthLessThanLengthFieldEndOffset(in, frameLength, lengthFieldEndOffset);
}
if (frameLength > maxFrameLength) {
exceededFrameLength(in, frameLength);
return null;
}
// never overflows because it's less than maxFrameLength
int frameLengthInt = (int) frameLength;
if (in.readableBytes() < frameLengthInt) {
return null;
}
if (initialBytesToStrip > frameLengthInt) {
failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, initialBytesToStrip);
}
in.skipBytes(initialBytesToStrip);
// extract frame
int readerIndex = in.readerIndex();
int actualFrameLength = frameLengthInt - initialBytesToStrip;
ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
in.readerIndex(readerIndex + actualFrameLength);
return frame;
}
private void exceededFrameLength(ByteBuf in, long frameLength) {
long discard = frameLength - in.readableBytes();
tooLongFrameLength = frameLength;
if (discard < 0) {
// buffer contains more bytes then the frameLength so we can discard all now
in.skipBytes((int) frameLength);
} else {
// Enter the discard mode and discard everything received so far.
discardingTooLongFrame = true;
bytesToDiscard = discard;
in.skipBytes(in.readableBytes());
}
failIfNecessary(true);
}
處理流程:
- 上一次數據包過長,并且有未讀取完的需要跳過的數據,則進行跳過處理;
- 如果可讀數據長度小于長度字段的下個字節偏移量,表示數據長度不夠,不做處理;
- 獲取長度字段并對長度字段進行處理,當長度小于0或小于長度字段下個字節偏移量,則進行失敗處理;
- 當包長度大于最大長度時,調用exceededFrameLength()進行數據跳過處理;
- 當可讀長度小于包中標識的長度,表示數據為讀完,不做處理;
- 當包長度小于需要跳過的長度,進行失敗處理;
- 讀取緩沖區中的數據;
2.7、其他解碼器
Netty內置了豐富的ChannelHandler,例如處理Http協議的Handler、處理Websocket協議的Handler、處理SSL安全套接字連接的Handler等等,此處不做進一步分析。
相關閱讀:
Netty源碼愫讀(一)ByteBuf相關源碼學習 【http://www.lxweimin.com/p/016daa404957】
Netty源碼愫讀(二)Channel相關源碼學習【http://www.lxweimin.com/p/02eac974258e】
Netty源碼愫讀(三)ChannelPipeline、ChannelHandlerContext相關源碼學習【http://www.lxweimin.com/p/be82d0fcdbcc】
Netty源碼愫讀(五)EventLoop與EventLoopGroup相關源碼學習【http://www.lxweimin.com/p/05096995d296】
Netty源碼愫讀(六)ServerBootstrap相關源碼學習【http://www.lxweimin.com/p/a71a9a0291f3】
參考書籍:
《Netty實戰》
《Netty權威指南》
參考博客:
http://www.lxweimin.com/p/4c35541eec10
http://www.lxweimin.com/p/0b79872eb515
http://www.lxweimin.com/p/a0a51fd79f62
http://www.wolfbe.com/detail/201609/379.html