1. TCP粘包、拆包圖解
假設(shè)客戶端分別發(fā)送了兩個(gè)數(shù)據(jù)包D1和D2給服務(wù)端,由于服務(wù)端一次讀取到字節(jié)數(shù)是不確定的,故可能存在以下四種情況:
1.服務(wù)端分兩次讀取到了兩個(gè)獨(dú)立的數(shù)據(jù)包,分別是D1和D2,沒有粘包和拆包
2.服務(wù)端一次接受到了兩個(gè)數(shù)據(jù)包,D1和D2粘合在一起,稱之為TCP粘包
3.服務(wù)端分兩次讀取到了數(shù)據(jù)包,第一次讀取到了完整的D1包和D2包的部分內(nèi)容,第二次讀取到了D2包的剩余內(nèi)容,這稱之為TCP拆包
4.服務(wù)端分兩次讀取到了數(shù)據(jù)包,第一次讀取到了D1包的部分內(nèi)容D1_1,第二次讀取到了D1包的剩余部分內(nèi)容D1_2和完整的D2包。
特別要注意的是,如果TCP的接受滑窗非常小,而數(shù)據(jù)包D1和D2比較大,很有可能會(huì)發(fā)生第五種情況,即服務(wù)端分多次才能將D1和D2包完全接受,期間發(fā)生多次拆包。
2. 粘包、拆包產(chǎn)生原因
2.1 滑動(dòng)窗口
TCP流量控制主要使用滑動(dòng)窗口協(xié)議,滑動(dòng)窗口是接受數(shù)據(jù)端使用的窗口大小,用來告訴發(fā)送端接口端的緩存大小,以此可以控制發(fā)送端發(fā)送數(shù)據(jù)的大小,從而達(dá)到流量控制的目的。這個(gè)窗口的大小就是我們以此傳輸幾個(gè)數(shù)據(jù)。對所有數(shù)據(jù)幀按順序賦予編號(hào)。發(fā)送方在發(fā)送過程中始終保持著一個(gè)發(fā)送窗口,只有落在發(fā)送方窗口的幀才允許被發(fā)送;同時(shí)接受方也維護(hù)一個(gè)窗口,只有落在接受窗口內(nèi)的幀才允許接收。
滑動(dòng)窗口是如何造成粘包、拆包的
粘包:假設(shè)發(fā)送方的每256 bytes表示一個(gè)完整的報(bào)文,接收方由于數(shù)據(jù)處理不及時(shí),這256個(gè)字節(jié)的數(shù)據(jù)都會(huì)被緩存到SO_RCVBUF(接收緩存區(qū))中。如果接收方的SO_RCVBUF中緩存了多個(gè)報(bào)文,那么對于接收方而言,這就是粘包。
拆包:考慮另外一種情況,假設(shè)接收方的窗口只剩了128,意味著發(fā)送方最多還可以發(fā)送128字節(jié),而由于發(fā)送方的數(shù)據(jù)大小是256字節(jié),因此只能發(fā)送前128字節(jié),等到接收方ack后,才能發(fā)送剩余字節(jié)。這就造成了拆包。
2.2 MSS和MTU分片
MSS: 是Maximum Segement Size縮寫,表示TCP報(bào)文中data部分的最大長度,是TCP協(xié)議在OSI五層網(wǎng)絡(luò)
模型中傳輸層對一次可以發(fā)送的最大數(shù)據(jù)的限制。
MTU: 最大傳輸單元是,Maxitum Transmission Unit的簡寫,是OSI五層網(wǎng)絡(luò)模型中鏈路層(datalink layer)對一次可以發(fā)送的最大數(shù)據(jù)的限制。
對于應(yīng)用層來說,只關(guān)心發(fā)送的數(shù)據(jù)DATA,將數(shù)據(jù)寫入socket在內(nèi)核中的發(fā)送緩沖區(qū)SO_SNDBUF即返回,操作系統(tǒng)會(huì)將SO_SNDBUF中的數(shù)據(jù)取出來進(jìn)行發(fā)送。傳輸層會(huì)在DATA前面加上TCP Header,構(gòu)成一個(gè)完整的TCP報(bào)文。
當(dāng)數(shù)據(jù)到達(dá)網(wǎng)絡(luò)層(network layer)時(shí),網(wǎng)絡(luò)層會(huì)在TCP報(bào)文的基礎(chǔ)上再添加一個(gè)IP Header,也就是將自己的網(wǎng)絡(luò)地址加入到報(bào)文中。到數(shù)據(jù)鏈路層時(shí),還會(huì)加上Datalink Header和CRC。
當(dāng)?shù)竭_(dá)物理層時(shí),會(huì)將SMAC(Source Machine,數(shù)據(jù)發(fā)送方的MAC地址),DMAC(Destination Machine,數(shù)據(jù)接受方的MAC地址 )和Type域加入。
可以發(fā)現(xiàn)數(shù)據(jù)在發(fā)送前,每一層都會(huì)在上一層的基礎(chǔ)上增加一些內(nèi)容,下圖演示了MSS、MTU在這個(gè)過程中的作用。
[圖片上傳失敗...(image-7a5058-1618037626721)]
MTU是以太網(wǎng)傳輸數(shù)據(jù)方面的限制,每個(gè)以太網(wǎng)幀都有最小的大小64bytes最大不能超過1518bytes。刨去以太網(wǎng)幀的幀頭 (DMAC目的MAC地址48bit=6Bytes+SMAC源MAC地址48bit=6Bytes+Type域2bytes)14Bytes和幀尾 CRC校驗(yàn)部分4Bytes(這個(gè)部分有時(shí)候大家也把它叫做FCS),那么剩下承載上層協(xié)議的地方也就是Data域最大就只能有1500Bytes這個(gè)值 我們就把它稱之為MTU。
由于MTU限制了一次最多可以發(fā)送1500個(gè)字節(jié),而TCP協(xié)議在發(fā)送DATA時(shí),還會(huì)加上額外的TCP Header和Ip Header,因此刨去這兩個(gè)部分,就是TCP協(xié)議一次可以發(fā)送的實(shí)際應(yīng)用數(shù)據(jù)的最大大小,也就是MSS
MSS長度 = MTU長度 - IP Header - TCP Header
TCP Header的長度是20字節(jié),IPv4中IP Header長度是20字節(jié),IPV6中IP Header長度是40字節(jié),因此:在IPV4中,以太網(wǎng)MSS可以達(dá)到1460byte;在IPV6中,以太網(wǎng)MSS可以達(dá)到1440byte。
需要注意的是MSS表示的一次可以發(fā)送的DATA的最大長度,而不是DATA的真實(shí)長度。發(fā)送方發(fā)送數(shù)據(jù)時(shí),當(dāng)SO_SNDBUF中的數(shù)據(jù)量大于MSS時(shí),操作系統(tǒng)會(huì)將數(shù)據(jù)進(jìn)行拆分,使得每一部分都小于MSS,這就是拆包,然后每一部分都加上TCP Header,構(gòu)成多個(gè)完整的TCP報(bào)文進(jìn)行發(fā)送,當(dāng)然經(jīng)過網(wǎng)絡(luò)層和數(shù)據(jù)鏈路層的時(shí)候,還會(huì)分別加上相應(yīng)的內(nèi)容。
需要注意: 默認(rèn)情況下,與外部通信的網(wǎng)卡的MTU大小是1500個(gè)字節(jié)。而本地回環(huán)地址的MTU大小為65535,這是因?yàn)楸镜販y試時(shí)數(shù)據(jù)不需要走網(wǎng)卡,所以不受到1500的限制。
3. Nagle算法
TCP/IP協(xié)議中,無論發(fā)送多少數(shù)據(jù),總是要在數(shù)據(jù)(DATA)前面加上協(xié)議頭(TCP Header+IP Header),同時(shí),對方接收到數(shù)據(jù),也需要發(fā)送ACK表示確認(rèn)。
即使從鍵盤輸入的一個(gè)字符,占用一個(gè)字節(jié),可能在傳輸上造成41字節(jié)的包,其中包括1字節(jié)的有用信息和40字節(jié)的首部數(shù)據(jù)。這種情況轉(zhuǎn)變成了4000%的消耗,這樣的情況對于重負(fù)載的網(wǎng)絡(luò)來是無法接受的。
為了盡可能的利用網(wǎng)絡(luò)帶寬,TCP總是希望盡可能的發(fā)送足夠大的數(shù)據(jù)。(一個(gè)連接會(huì)設(shè)置MSS參數(shù),因此,TCP/IP希望每次都能夠以MSS尺寸的數(shù)據(jù)塊來發(fā)送數(shù)據(jù))。
3.1 Nagle算法——盡可能發(fā)送大塊數(shù)據(jù),避免網(wǎng)絡(luò)中充斥著許多小數(shù)據(jù)塊
3.2 Nagle算法的規(guī)則:
- 如果SO_SNDBUF(發(fā)送緩沖區(qū))中的數(shù)據(jù)長度達(dá)到MSS,則允許發(fā)送;
- 如果該SO_SNDBUF中含有FIN,表示請求關(guān)閉連接,則先將SO_SNDBUF中的剩余數(shù)據(jù)發(fā)送,再關(guān)閉;
- 設(shè)置了TCP_NODELAY=true選項(xiàng),則允許發(fā)送。TCP_NODELAY是取消TCP的確認(rèn)延遲機(jī)制,相當(dāng)于禁用了Nagle 算法。
- 未設(shè)置TCP_CORK選項(xiàng)時(shí),若所有發(fā)出去的小數(shù)據(jù)包(包長度小于MSS)均被確認(rèn),則允許發(fā)送;
上述條件都未滿足,但發(fā)生了超時(shí)(一般為200ms),則立即發(fā)送。
4. 粘包問題的解決策略
- 消息定長,每個(gè)報(bào)文固定長度,不夠的空格補(bǔ)齊
- 在包尾部增加換車換行進(jìn)行分割
- 將消息提分為消息頭和消息體,消息頭中包含表示總長度
- 更復(fù)雜的應(yīng)用層協(xié)議
5. netty 解決TCP粘包問題
拆包原理:
- 如果當(dāng)前讀取的數(shù)據(jù)不足以拼接成一個(gè)完整的業(yè)務(wù)數(shù)據(jù)包,那就保留該數(shù)據(jù),繼續(xù)從tcp緩沖區(qū)中讀取,直到得到一個(gè)完整的數(shù)據(jù)包
- 如果當(dāng)前讀到的數(shù)據(jù)加上已經(jīng)讀取的數(shù)據(jù)足夠拼接成一個(gè)數(shù)據(jù)包,那就將已經(jīng)讀取的數(shù)據(jù)拼接上本次讀取的數(shù)據(jù),夠成一個(gè)完整的業(yè)務(wù)數(shù)據(jù)包傳遞到業(yè)務(wù)邏輯,多余的數(shù)據(jù)仍然保留,以便和下次讀到的數(shù)據(jù)嘗試拼接
5.1 ByteToMessageDecoder
ByteToMessageDecoder
:netty 中的拆包也是如上這個(gè)原理,內(nèi)部會(huì)有一個(gè)累加器,每次讀取到數(shù)據(jù)都會(huì)不斷累加,然后嘗試對累加到的數(shù)據(jù)進(jìn)行拆包,拆成一個(gè)完整的業(yè)務(wù)數(shù)據(jù)包,這個(gè)基類叫做 ByteToMessageDecoder
累加器
ByteToMessageDecoder
中定義了兩個(gè)累加器
public static final Cumulator MERGE_CUMULATOR = ...;
public static final Cumulator COMPOSITE_CUMULATOR = ...;
MERGE_CUMULATOR
的原理是每次都將讀取到的數(shù)據(jù)通過內(nèi)存拷貝的方式,拼接到一個(gè)大的字節(jié)容器中,這個(gè)字節(jié)容器在 ByteToMessageDecoder
中叫做 cumulation
。
public static final Cumulator MERGE_CUMULATOR = new Cumulator() {
@Override
public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
if (!cumulation.isReadable() && in.isContiguous()) {
// If cumulation is empty and input buffer is contiguous, use it directly
cumulation.release();
return in;
}
try {
final int required = in.readableBytes();
// 判斷是否需要擴(kuò)容
if (required > cumulation.maxWritableBytes() ||
(required > cumulation.maxFastWritableBytes() && cumulation.refCnt() > 1) ||
cumulation.isReadOnly()) {
// Expand cumulation (by replacing it) under the following conditions:
// - cumulation cannot be resized to accommodate the additional data
// - cumulation can be expanded with a reallocation operation to accommodate but the buffer is
// assumed to be shared (e.g. refCnt() > 1) and the reallocation may not be safe.
return expandCumulation(alloc, cumulation, in);
}
//累加
cumulation.writeBytes(in, in.readerIndex(), required);
in.readerIndex(in.writerIndex());
return cumulation;
} finally {
// We must release in in all cases as otherwise it may produce a leak if writeBytes(...) throw
// for whatever release (for example because of OutOfMemoryError)
in.release();
}
}
};
擴(kuò)容:
static ByteBuf expandCumulation(ByteBufAllocator alloc, ByteBuf oldCumulation, ByteBuf in) {
int oldBytes = oldCumulation.readableBytes();
int newBytes = in.readableBytes();
int totalBytes = oldBytes + newBytes;
ByteBuf newCumulation = alloc.buffer(alloc.calculateNewCapacity(totalBytes, MAX_VALUE));
ByteBuf toRelease = newCumulation;
try {
// This avoids redundant checks and stack depth compared to calling writeBytes(...)
newCumulation.setBytes(0, oldCumulation, oldCumulation.readerIndex(), oldBytes)
.setBytes(oldBytes, in, in.readerIndex(), newBytes)
.writerIndex(totalBytes);
in.readerIndex(in.writerIndex());
toRelease = oldCumulation;
return newCumulation;
} finally {
toRelease.release();
}
}
擴(kuò)容也是一個(gè)內(nèi)存拷貝操作,新增的大小即是新讀取數(shù)據(jù)的大小
拆包
累加器原理清楚之后。回到主流程,channelRead
方法,channelRead
方法是每次從TCP緩沖區(qū)讀到數(shù)據(jù)都會(huì)調(diào)用的方法,觸發(fā)點(diǎn)在AbstractNioByteChannel
的read
方法中,里面有個(gè)while
循環(huán)不斷讀取,讀取到一次就觸發(fā)一次channelRead
。
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
CodecOutputList out = CodecOutputList.newInstance();
try {
//1.累加數(shù)據(jù)
first = cumulation == null;
cumulation = cumulator.cumulate(ctx.alloc(), first ? Unpooled.EMPTY_BUFFER : cumulation, (ByteBuf) msg);
//2. 將累加到的數(shù)據(jù)傳遞給業(yè)務(wù)進(jìn)行拆包
callDecode(ctx, cumulation, out);
} catch (DecoderException e) {
throw e;
} catch (Exception e) {
throw new DecoderException(e);
} finally {
try {
//3.netty會(huì)在每次讀取到一次數(shù)據(jù),業(yè)務(wù)拆包之后對字節(jié)字節(jié)容器做清理,清理部分的代碼如下
if (cumulation != null && !cumulation.isReadable()) {
//如果字節(jié)容器當(dāng)前已無數(shù)據(jù)可讀取,直接銷毀字節(jié)容器,并且標(biāo)注一下當(dāng)前字節(jié)容器一次數(shù)據(jù)也沒讀取
numReads = 0;
cumulation.release();
cumulation = null;
} else if (++numReads >= discardAfterReads) {
// 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
//字節(jié)容器中仍然有未被業(yè)務(wù)拆包器讀取的數(shù)據(jù),那就做一次壓縮,有效數(shù)據(jù)段整體移到容器首部
numReads = 0;
discardSomeReadBytes();
}
//4. 傳遞業(yè)務(wù)數(shù)據(jù)包給業(yè)務(wù)解碼器處理
int size = out.size();
firedChannelRead |= out.insertSinceRecycled();
fireChannelRead(ctx, out, size);
} finally {
out.recycle();
}
}
} else {
ctx.fireChannelRead(msg);
}
}
方法體可以分為以下幾個(gè)邏輯步驟
1.累加數(shù)據(jù)
2.將累加到的數(shù)據(jù)傳遞給業(yè)務(wù)進(jìn)行業(yè)務(wù)拆包
3.清理字節(jié)容器
4.傳遞業(yè)務(wù)數(shù)據(jù)包給業(yè)務(wù)解碼器處理
壓縮,有效數(shù)據(jù)段整體移到容器首部
discardSomeReadBytes之前,字節(jié)累加器中的數(shù)據(jù)分布
+--------------+----------+----------+
| readed | unreaded | writable |
+--------------+----------+----------+
discardSomeReadBytes之后,字節(jié)容器中的數(shù)據(jù)分布
+----------+-------------------------+
| unreaded | writable |
+----------+-------------------------+
這樣字節(jié)容器又可以承載更多的數(shù)據(jù)了
callDecode 字節(jié)容器的數(shù)據(jù)拆分成業(yè)務(wù)數(shù)據(jù)包塞到業(yè)務(wù)數(shù)據(jù)容器out中
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();
//進(jìn)行拆包 傳進(jìn)去的是當(dāng)前讀取到的未被消費(fèi)的所有的數(shù)據(jù),以及業(yè)務(wù)協(xié)議包容器
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()) {
//一個(gè)是拆包器什么數(shù)據(jù)也沒讀取,可能數(shù)據(jù)還不夠業(yè)務(wù)拆包器處理,直接break等待新的數(shù)據(jù)
if (oldInputLength == in.readableBytes()) {
break;
} else {
//拆包器已讀取部分?jǐn)?shù)據(jù),說明解碼器仍然在工作,繼續(xù)解碼
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);
}
}
5.LineBasedFramDecoder源碼
ByteOrder byteOrder
ByteOrder.BIG_ENDIAN
int maxFrameLength
Integer.MAX_VALUE 包的最大長度,超出包的最大長度netty將會(huì)做一些特殊處理
int lengthFieldOffset
0 長度域的偏移量
int lengthFieldLength
4 長度域長度
int lengthAdjustment
0 長度域的偏移量矯正。 如果長度域的值,除了包含有效數(shù)據(jù)域的長度外,還包含了其他域(如長度域自身)長度,那么,就需要進(jìn)行矯正。矯正的值為:包長 - 長度域的值 – 長度域偏移 – 長度域長。
int initialBytesToStrip
12 表示netty拿到一個(gè)完整的數(shù)據(jù)包之后向業(yè)務(wù)解碼器傳遞之前,應(yīng)該跳過多少字節(jié)
boolean failFast
true 超過最大maxFrameLength是是否報(bào)錯(cuò)
https://blog.csdn.net/john1337/article/details/102806307
1.基于長度的拆包
上面這類數(shù)據(jù)包協(xié)議比較常見的,前面幾個(gè)字節(jié)表示數(shù)據(jù)包的長度(不包括長度域),后面是具體的數(shù)據(jù)。拆完之后數(shù)據(jù)包是一個(gè)完整的帶有長度域的數(shù)據(jù)包(之后即可傳遞到應(yīng)用層解碼器進(jìn)行解碼),創(chuàng)建一個(gè)如下方式的
LengthFieldBasedFrameDecoder
即可實(shí)現(xiàn)這類協(xié)議
new LengthFieldBasedFrameDecoder(Integer.MAX, 0, 4);
/**
* @param maxFrameLength 包的最大長度
* the maximum length of the frame. If the length of the frame is
* greater than this value, {@link TooLongFrameException} will be
* thrown.
* @param lengthFieldOffset 長度域的偏移量 在這里是0,表示無偏移
* the offset of the length field
* @param lengthFieldLength 長度域長度 這里是4,表示長度域的長度為4
* the length of the length field
*/
public LengthFieldBasedFrameDecoder(
int maxFrameLength,
int lengthFieldOffset, int lengthFieldLength) {
this(maxFrameLength, lengthFieldOffset, lengthFieldLength, 0, 0);
}
2. 基于長度的截?cái)嗖鸢?/h2>
度域被截掉,我們只需要指定另外一個(gè)參數(shù)就可以實(shí)現(xiàn),這個(gè)參數(shù)叫做
initialBytesToStrip
,表示netty拿到一個(gè)完整的數(shù)據(jù)包之后向業(yè)務(wù)解碼器傳遞之前,應(yīng)該跳過多少字節(jié)
new LengthFieldBasedFrameDecoder(Integer.MAX, 0, 4, 0, 4);
int maxFrameLength
int lengthFieldOffset
int lengthFieldLength
int lengthAdjustment
int initialBytesToStrip
3.基于偏移長度的拆包
下面這種方式二進(jìn)制協(xié)議是更為普遍的,前面幾個(gè)固定字節(jié)表示協(xié)議頭,通常包含一些magicNumber,protocol version 之類的meta信息,緊跟著后面的是一個(gè)長度域,表示包體有多少字節(jié)的數(shù)據(jù)
只需要基于第一種情況,調(diào)整第二個(gè)參數(shù)既可以實(shí)現(xiàn)
new LengthFieldBasedFrameDecoder(Integer.MAX, 4, 4);
lengthFieldOffset
是4,表示跳過4個(gè)字節(jié)之后的才是長度域
4.基于可調(diào)整長度的拆包
即長度域在前,header在后,這種情況又是如何來調(diào)整參數(shù)達(dá)到我們想要的拆包效果呢?
1.長度域在數(shù)據(jù)包最前面表示無偏移,lengthFieldOffset 為 0
2.長度域的長度為3,即lengthFieldLength為3
2.長度域表示的包體的長度略過了header,這里有另外一個(gè)參數(shù),叫做 lengthAdjustment,包體長度調(diào)整的大小,長度域的數(shù)值表示的長度加上這個(gè)修正值表示的就是帶header的包,這里是 12+2,header和包體一共占14個(gè)字節(jié)
new LengthFieldBasedFrameDecoder(Integer.MAX, 0, 3, 2, 0);
5.基于偏移可調(diào)整長度的截?cái)嗖鸢?/h2>
更變態(tài)一點(diǎn)的二進(jìn)制協(xié)議帶有兩個(gè)header,比如下面這種
拆完之后,HDR1 丟棄,長度域丟棄,只剩下第二個(gè)header和有效包體,這種協(xié)議中,一般HDR1可以表示magicNumber,表示應(yīng)用只接受以該magicNumber開頭的二進(jìn)制數(shù)據(jù),rpc里面用的比較多
我們?nèi)匀豢梢酝ㄟ^設(shè)置netty的參數(shù)實(shí)現(xiàn)
1.長度域偏移為1,那么 lengthFieldOffset
為1
2.長度域長度為2,那么lengthFieldLength
為2
3.長度域表示的包體的長度略過了HDR2
,但是拆包的時(shí)候HDR2也被netty當(dāng)作是包體的的一部分來拆,HDR2的長度為1,那么lengthAdjustment
為1
4.拆完之后,截掉了前面三個(gè)字節(jié),那么initialBytesToStrip
為 3 (長度為2 + HDR2為1 = 3)
最后,代碼實(shí)現(xiàn)為
new LengthFieldBasedFrameDecoder(Integer.MAX, 1, 2, 1, 3);
6.LengthFieldBasedFrameDecoder 源碼
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
//discardingTooLongFrame 默認(rèn)初始化為false
if (discardingTooLongFrame) {
discardingTooLongFrame(in);
}
//in.readableBytes() 可以字節(jié)的長度
//lengthFieldEndOffset 長度字段結(jié)束偏移量
//如果當(dāng)前可讀的字節(jié)數(shù)< 長度域開始的字節(jié)數(shù) 返回等待, null表示返回等待
if (in.readableBytes() < lengthFieldEndOffset) {
return null;
}
// 拿到長度域的實(shí)際字節(jié)偏移
int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
//得到 未調(diào)整的數(shù)據(jù)幀長度 其實(shí)就是讀物數(shù)據(jù)域標(biāo)識(shí)的值,代表本次內(nèi)容的數(shù)據(jù)長度
// 拿到實(shí)際的未調(diào)整過的包長度
long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);
if (frameLength < 0) {
failOnNegativeLengthField(in, frameLength, lengthFieldEndOffset);
}
//機(jī)上偏移量
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;
//如果當(dāng)前可讀字節(jié) < 本次數(shù)據(jù)包的字節(jié)數(shù),則說明發(fā)生了粘包問題,返回 等待下一個(gè)包 然后讀取完成數(shù)組
if (in.readableBytes() < frameLengthInt) {
return null;
}
//需要丟棄的字節(jié)數(shù) > 當(dāng)前數(shù)據(jù)包的長度 拋出異常
if (initialBytesToStrip > frameLengthInt) {
failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, initialBytesToStrip);
}
//跳過需要丟棄的字節(jié)數(shù)
in.skipBytes(initialBytesToStrip);
// extract frame
int readerIndex = in.readerIndex();
//actualFrameLength 數(shù)據(jù)包有用數(shù)據(jù)的長度
int actualFrameLength = frameLengthInt - initialBytesToStrip;
//讀取數(shù)據(jù)
ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
in.readerIndex(readerIndex + actualFrameLength);
return frame;
}
// 數(shù)據(jù)包長度超出最大包長度,進(jìn)入丟棄模式
private void exceededFrameLength(ByteBuf in, long frameLength) {
long discard = frameLength - in.readableBytes();
tooLongFrameLength = frameLength;
if (discard < 0) {
// 當(dāng)前可讀字節(jié)已達(dá)到frameLength,直接跳過frameLength個(gè)字節(jié),丟棄之后,后面有可能就是一個(gè)合法的數(shù)據(jù)包
// buffer contains more bytes then the frameLength so we can discard all now
in.skipBytes((int) frameLength);
} else {
// 當(dāng)前可讀字節(jié)未達(dá)到frameLength,說明后面未讀到的字節(jié)也需要丟棄,進(jìn)入丟棄模式,先把當(dāng)前累積的字節(jié)全部丟棄
// Enter the discard mode and discard everything received so far.
discardingTooLongFrame = true;
// bytesToDiscard表示下次還需要丟棄多少字節(jié)
bytesToDiscard = discard;
in.skipBytes(in.readableBytes());
}
failIfNecessary(true);
}
http://www.lxweimin.com/p/a0a51fd79f62
https://blog.csdn.net/e_wsq/article/details/77854547
https://www.cnblogs.com/651434092qq/p/11067528.html