消息粘包 和 消息不完整 問題
消息粘包 和 消息不完整問題 其實都是應用層會帶來的問題,和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小屋