訊息粘包 和 訊息不完整 問題
訊息粘包 和 訊息不完整問題 其實都是應用層會帶來的問題,和TCP 沒關係,TCP 是能夠保證訊息的順序 和 完整性的
本篇只是簡單說明一下 什麼是 訊息粘包 和 訊息不完整問題
1.復現訊息粘包 和 訊息不完成 問題
先來看看 出現了什麼問題導致 需要去處理 訊息粘包 和 訊息完整 問題 ,前面通過NIO改造了聊天室的案例中,我們開復現一下 訊息粘包 和 訊息不完整
1.1 復現 訊息粘包問題
Client端傳送多條資料
程式碼還是原來的Client端程式碼 ,只是在傳送資料的時候 一次性發了100條
public static void connectServer(ServerInfo serverInfo) {
try {
// 開啟 tcp 連線
socket = new Socket(Inet4Address.getByName(serverInfo.getIp()), serverInfo.getPort());
// 開啟 執行緒 非同步 讀取 server message
clientReadHandler =
new ClientReadHandler(socket.getInputStream(), ClientConnectTcp::close);
clientReadHandler.start();
// 監聽 鍵盤寫入 system.in
systemInReader = new BufferedReader(new InputStreamReader(System.in));
clientWriteHandler = new ClientWriteHandler(socket.getOutputStream());
do {
String message = systemInReader.readLine();
// 非同步傳送 到 server
if (message != null) {
//把讀取到的message 傳送100次 並且後面新增上i標識
for (int i = 0; i < 100; i++) {
clientWriteHandler.sendMsg(message + ":" + i);
}
}
if (CommonConstants.BYE_FLAG.equals(message)) {
close();
}
} while (!doReadFlag);
} catch (IOException e) {
log.error("【建立tcp 連線異常:{}】", e.getMessage());
} finally {
close();
}
}
Server端 接受到的訊息
可以看到 出現了嚴重的粘包問題,原本我們希望 訊息是一條一條處理,如下:
receiveAsync message:abcdefg:0
receiveAsync message:abcdefg:1
receiveAsync message:abcdefg:2
10:12:21.721 [read-io-executors1] INFO com.johnny.chatroom.lib.nio.Connector - receiveAsync message:abcdefg:0
abcdefg:1
abcdefg:2
abcdefg:3
abcdefg:4
abcdefg:5
abcdefg:6
abcdefg:7
abcdefg:8
abcdefg:9
abcdefg:10
abcdefg:11
abcdefg:12
abcdefg:13
abcdefg:14
abcdefg:15
abcdefg:16
abcdefg:17
abcdefg:18
abcdefg:19
abcdefg:20
abcdefg:21
abcdefg:22
abcdefg:23
a
10:12:21.722 [read-io-executors2] INFO com.johnny.chatroom.lib.nio.Connector - receiveAsync message:cdefg:24
abcdefg:25
abcdefg:26
1.2 復現 訊息不完整問題
修改Server 伺服器端的 IoArgs 的 ByteBuffer的 緩衝區大小
@Slf4j
@Data
public class IoArgs {
//緩衝區大小 從 256個位元組 改成 4 個位元組
private ByteBuffer byteBuffer = ByteBuffer.allocate(4);
其他程式碼省略...
}
Client客戶端還是如下 傳送100條資料
do {
String message = systemInReader.readLine();
// 非同步傳送 到 server
if (message != null) {
for (int i = 0; i < 100; i++) {
clientWriteHandler.sendMsg(message + ":" + i);
}
}
if (CommonConstants.BYE_FLAG.equals(message)) {
close();
}
} while (!doReadFlag);
Server端 接受到的訊息
可以看到 原本一條訊息 abcdefg 被拆開成了 很多子訊息了。。出現了 嚴重的 訊息不完整問題
10:19:38.754 [read-io-executors1] INFO com.johnny.chatroom.lib.nio.Connector - receiveAsync message:abc
10:19:38.754 [read-io-executors2] INFO com.johnny.chatroom.lib.nio.Connector - receiveAsync message:efg
10:19:38.755 [read-io-executors3] INFO com.johnny.chatroom.lib.nio.Connector - receiveAsync message:0
a
10:19:38.755 [read-io-executors4] INFO com.johnny.chatroom.lib.nio.Connector - receiveAsync message:cde
10:19:38.756 [read-io-executors1] INFO com.johnny.chatroom.lib.nio.Connector - receiveAsync message:g:1
10:19:38.756 [read-io-executors2] INFO com.johnny.chatroom.lib.nio.Connector - receiveAsync message:abc
10:19:38.756 [read-io-executors3] INFO com.johnny.chatroom.lib.nio.Connector - receiveAsync message:efg
10:19:38.756 [read-io-executors4] INFO com.johnny.chatroom.lib.nio.Connector - receiveAsync message:2
a
10:19:38.756 [read-io-executors1] INFO com.johnny.chatroom.lib.nio.Connector - receiveAsync message:cde
10:19:38.757 [read-io-executors2] INFO com.johnny.chatroom.lib.nio.Connector - receiveAsync message:g:3
10:19:38.757 [read-io-executors3] INFO com.johnny.chatroom.lib.nio.Connector - receiveAsync message:abc
10:19:38.757 [read-io-executors4] INFO com.johnny.chatroom.lib.nio.Connector - receiveAsync message:efg
10:19:38.757 [read-io-executors1] INFO com.johnny.chatroom.lib.nio.Connector - receiveAsync message:4
2. 訊息粘包 和 訊息不完成 問題概述
在socket網路程式設計中,都是端到端通訊,由客戶端埠+服務端埠+客戶端IP+服務端IP+傳輸協議組成的五元組可以明確的標識一條連線。在TCP的socket程式設計中,傳送端和接收端都有成對的socket。傳送端為了將多個發往接收端的包,更加高效的的發給接收端,於是採用了優化演算法(Nagle演算法),將多次間隔較小、資料量較小的資料,合併成一個資料量大的資料塊,然後進行封包。那麼這樣一來,接收端就必須使用高效科學的拆包機制來分辨這些資料。
2.1 什麼是TCP粘包問題?
TCP粘包就是指傳送方傳送的若干包資料到達接收方時粘成了一包,從接收緩衝區來看,後一包資料的頭緊接著前一包資料的尾,出現粘包的原因是多方面的,可能是來自傳送方,也可能是來自接收方。
2.2 造成TCP粘包的原因
-
傳送方原因
TCP預設使用Nagle演算法(主要作用:減少網路中報文段的數量),而Nagle演算法主要做兩件事:
只有上一個分組得到確認,才會傳送下一個分組
收集多個小分組,在一個確認到來時一起傳送
Nagle演算法造成了傳送方可能會出現粘包問題 -
接收方原因
TCP接收到資料包時,並不會馬上交到應用層進行處理,或者說應用層並不會立即處理。實際上,TCP將接收到的資料包儲存在接收快取裡,然後應用程式主動從快取讀取收到的分組。這樣一來,如果TCP接收資料包到快取的速度大於應用程式從快取中讀取資料包的速度,多個包就會被快取,應用程式就有可能讀取到多個首尾相接粘到一起的包。
-
什麼時候需要處理粘包現象?
如果傳送方傳送的多組資料本來就是同一塊資料的不同部分,比如說一個檔案被分成多個部分傳送,這時當然不需要處理粘包現象
如果多個分組毫不相干,甚至是並列關係,那麼這個時候就一定要處理粘包現象了
2.3 如何處理粘包現象?
-
傳送方
對於傳送方造成的粘包問題,可以通過關閉Nagle演算法來解決,使用TCP_NODELAY選項來關閉演算法
-
接收方
接收方沒有辦法來處理粘包現象,只能將問題交給應用層來處理。
-
應用層
應用層的解決辦法簡單可行,不僅能解決接收方的粘包問題,還可以解決傳送方的粘包問題。
1.固定包長度的資料包
2.以指定字元(串)為包的結束標誌 如換行符 \n
3.包頭 + 包體格式 這種格式的包一般分為兩部分,即包頭和包體,包頭是固定大小的,且包頭中必須含有一個欄位來說明接下來的包體有多大。
總結
本篇簡單概述了一下 什麼是 訊息粘包 和 訊息不完整問題,並且通過程式碼 復現了一下出現的問題,那麼具體的處理粘包等問題,後面再寫,核心思想就是 通過讀取包頭獲取要讀取的資料包的長度,然後根據長度去讀取後面的資料,不夠就先快取 等待下一個包來,足夠了長度就丟給上層處理,既解決了 訊息粘包 也能解決訊息不完整問題,具體程式碼演示 下一篇再述
歡迎大家訪問 個人部落格 Johnny小屋