當前位置:
首頁 > 知識 > 使用包定長FixedLengthFrameDecoder解決半包粘包

使用包定長FixedLengthFrameDecoder解決半包粘包

1 試驗

由於客戶端發給伺服器端的是hello server,im a client字元串,該字元串佔用24位元組,所以在伺服器端channelpipeline裡面添加一個長度為24的定長解碼器和二進位轉換為string的解碼器:

使用包定長FixedLengthFrameDecoder解決半包粘包

然後修改NettyServerHandler的channelRead如下:

使用包定長FixedLengthFrameDecoder解決半包粘包

由於伺服器發給客戶端的是hello client ,im server字元串,該字元串佔用23位元組,所以在客戶端端channelpipeline裡面添加一個長度為23的定長解碼器和二進位轉換為string的解碼器:

使用包定長FixedLengthFrameDecoder解決半包粘包

然後修改NettyClientHandler的channelRead如下:

使用包定長FixedLengthFrameDecoder解決半包粘包

然後重新啟動伺服器客戶端,結果如下:

伺服器端結果:

----Server Started----
--- accepted client---
0receive client info: hello server,im a client
send info to client:hello client ,im server
1receive client info: hello server,im a client
send info to client:hello client ,im server
2receive client info: hello server,im a client
send info to client:hello client ,im server
3receive client info: hello server,im a client
send info to client:hello client ,im server
4receive client info: hello server,im a client
send info to client:hello client ,im server
5receive client info: hello server,im a client
send info to client:hello client ,im server
6receive client info: hello server,im a client
send info to client:hello client ,im server
7receive client info: hello server,im a client
send info to client:hello client ,im server
8receive client info: hello server,im a client
send info to client:hello client ,im server
9receive client info: hello server,im a client
send info to client:hello client ,im server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

客戶端結果:

--- client already connected----
0receive from server:hello client ,im server
1receive from server:hello client ,im server
2receive from server:hello client ,im server
3receive from server:hello client ,im server
4receive from server:hello client ,im server
5receive from server:hello client ,im server
6receive from server:hello client ,im server
7receive from server:hello client ,im server
8receive from server:hello client ,im server
9receive from server:hello client ,im server
1
2
3
4
5
6
7
8
9
10
11

可知使用FixedLengthFrameDecoder已經解決了半包粘包問題。

2 FixedLengthFrameDecoder的原理

顧名思義是使用包定長方式來解決粘包半包問題,假設服務端接受到下面四個包分片:

使用包定長FixedLengthFrameDecoder解決半包粘包

那麼使用FixedLengthFrameDecoder(3)會將接受buffer裡面的上面數據解碼為下面固定長度為3的3個包

使用包定長FixedLengthFrameDecoder解決半包粘包

FixedLengthFrameDecoder是繼承自 ByteToMessageDecoder類的,當伺服器接受buffer數據就緒後會調用ByteToMessageDecoder的channelRead方法進行讀取,下面我們從這個函數開始講解:

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//4.2.1
if (msg instanceof ByteBuf) {
CodecOutputList out = CodecOutputList.newInstance();
try {
...
//4.2.2
callDecode(ctx, cumulation, out);
} catch (DecoderException e) {
throw e;
} catch (Throwable t) {
throw new DecoderException(t);
} finally {
...
}
} else {
//4.2.3
ctx.fireChannelRead(msg);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

如上代碼4.2.2具體是方法callDecode進行數據讀取的,其代碼如下:

protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
try {
//4.2.4
while (in.isReadable()) {
int outSize = out.size();
//4.2.4.1
if (outSize > 0) {
fireChannelRead(ctx, out, outSize);
out.clear();
...
}
//4.2.4.2
int oldInputLength = in.readableBytes();
decodeRemovalReentryProtection(ctx, in, out);
...
//4.2.4.3
if (outSize == out.size()) {
if (oldInputLength == in.readableBytes()) {
break;
} else {
continue;
}
}
...
//4.2.4.4
if (isSingleDecode()) {
break;
}
}
} catch (DecoderException e) {
throw e;
} catch (Throwable cause) {
throw new DecoderException(cause);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

如上代碼callDecode中4.2.4是使用循環進行讀取,這是因為可能出現粘包情況,使用循環可以逐個對單包進行處理。

其中4.2.4.1判斷如果讀取了包則調用fireChannelRead激活channelpipeline裡面的其它handler的channelRead方法,因為這裡,FixedLengthFrameDecoder只是channelpipeline中的一個handler。

代碼4.2.4.2的decodeRemovalReentryProtection方法作用是調用FixedLengthFrameDecoder的decode方法具體從接受buffer讀取數據,後者代碼如下:

protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
Object decoded = decode(ctx, in);//4.2.6
if (decoded != null) {
out.add(decoded);
}
}
protected Object decode(
@SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {
if (in.readableBytes() < frameLength) {
return null;
} else {
return in.readRetainedSlice(frameLength);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

如上代碼4.2.6如果發現接受buffer裡面的位元組數小於我們設置的固定長度frameLength則說明出現了半包情況,則直接返回null;否者讀取固定長度的位元組數。

然後執行代碼4.2.4.3,其判斷outSize == out.size()說明代碼4.2.6沒有讀取一個包(說明出現了半包),則看當前buffer緩存的位元組數是否變化了,如果沒有變化則結束循環讀取,如果變化了則可能之前的半包已經變成了全包,則需要再次調用4.2.6進行讀取判斷。

代碼4.2.4.4判斷是否只需要讀取單個包(默認false),如果是則讀取一個包後就跳出循環,也就是如果出現了粘包現象,在一次channelRead事件到來後並不會循環讀取所有的包,而是讀取最先到的一個包,那麼buffer裡面剩餘的包要等下一次channelRead事件到了時候在讀取。

最後

想了解JDK NIO和更多Netty基礎的可以單擊我

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

XML Schema 簡介
XML 的元素

TAG:程序員小新人學習 |