先理解核心問題:什么是“TCP粘包”?
TCP 就像一條水管,數據通過水管從一端傳到另一端。但它有個特點:不會按“發送時的小包”來劃分,而是把數據當成連續的字節流。
比如:
- 你分兩次發數據:第一次發“hello”(5字節),第二次發“world”(5字節);
- 接收方可能一次收到“helloworld”(10字節,兩個包粘在一起),也可能第一次收到“hel”(3字節),第二次收到“loworld”(7字節)。
這種“數據沒按預期分成小包,要么粘在一起,要么被拆成碎塊”的情況,就叫 “粘包”。如果直接用普通的 read
函數,可能讀不全或讀多了,導致數據解析錯誤。
寫個 Readn
函數:保證“讀夠指定的字節數”
不管數據是粘在一起還是被拆成碎塊,Readn
都能確保你讀到 “正好想要的字節數”。比如你要讀10字節,它就一直等,直到湊夠10字節才返回。
逐行說明:
ssize_t Readn(int fd, void* buf, size_t n)
// 參數:fd(要讀的連接,比如客戶端連接);buf(裝數據的緩沖區);n(要讀的字節數,比如10)
{size_t nleft = n; // 記錄“還剩多少字節沒讀”(一開始等于n,比如10)while(nleft > 0) { // 只要還有沒讀完的,就繼續循環// 調用系統的read函數,試著讀剩下的字節(nleft)ssize_t nread = read(fd, buf, nleft);if(nread < 0) { // 讀數據出錯了if(errno == EINTR) continue; // 如果是“被信號打斷”(比如系統臨時有事),就重試return -1; // 其他錯誤(比如連接斷了),返回-1表示失敗}// 讀到了nread字節(比如第一次讀了3字節)buf = (char*)buf + nread; // 緩沖區指針往后移nread位(下次從新位置開始裝)nleft -= nread; // 剩余字節數減少nread(比如10-3=7,還剩7字節要讀)}return n; // 循環結束,說明讀夠了n字節,返回總字節數
}
舉例說明:怎么解決粘包?
比如你要從客戶端讀10字節數據,可能遇到兩種情況:
-
數據被拆成多段:
- 第一次
read
只讀到3字節,nleft
變成7,繼續循環; - 第二次
read
讀到5字節,nleft
變成2,繼續循環; - 第三次
read
讀到2字節,nleft
變成0,循環結束,返回10(成功讀夠)。
- 第一次
-
數據粘在一起:
- 第一次
read
就讀到15字節(比需要的10字節多),但Readn
只取前10字節,nleft
變成0,循環結束,剩下的5字節會留給下一次讀取。
- 第一次
總結:
Readn
就像一個“執著的收快遞員”:你說要10個包裹,它就一直等,哪怕快遞分好幾次到,也會湊齊10個再交給你,絕不會少給;如果快遞多送了,就先收下需要的10個,剩下的下次再處理。
這樣就能完美解決TCP粘包問題,確保程序讀到的數據是完整、準確的,不會因為傳輸時的“拆包”“粘包”導致解析錯誤。