訊息粘包 和 訊息不完整 問題

AskaJohnny發表於2022-03-30

訊息粘包 和 訊息不完整 問題

訊息粘包 和 訊息不完整問題 其實都是應用層會帶來的問題,和TCP 沒關係,TCP 是能夠保證訊息的順序 和 完整性的

本篇只是簡單說明一下 什麼是 訊息粘包 和 訊息不完整問題

image-20220330104603542

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小屋

相關文章