如何解決粘包問題?
??關注“博文視點Broadview”,獲取更多書訊

進行技術面試時,面試官經(jīng)常會問:“網(wǎng)絡通信時,如何解決粘包、丟包或者包亂序問題?”
這其實考察的就是網(wǎng)絡基礎知識。
如果使用 TCP 進行通信,則在大多數(shù)場景下是不存在丟包和包亂序問題的。
因為TCP通信是可靠的通信方式,TCP棧通過序列號和包重傳確認機制保證數(shù)據(jù)包的有序和一定被正確發(fā)送到目的地;如果使用UDP進行通信,且不允許少量丟包,就要自己在UDP的基礎上實現(xiàn)類似TCP這種有序和可靠的傳輸機制了(例如RTP、RUDP)。所以將該問題拆解后,就只剩下如何解決粘包的問題。

什么是粘包?
粘包就是連續(xù)向對端發(fā)送兩個或者兩個以上的數(shù)據(jù)包,對端在一次收取中收到的數(shù)據(jù)包數(shù)量可能大于1個,當大于1個時,可能是幾個(包括一個)包加上某個包的部分,或者干脆幾個完整的包在一起。當然,也可能收到的數(shù)據(jù)只是一個包的部分,這種情況一般也叫作半包。
粘包示意圖如下圖所示。

無論是半包問題還是粘包問題,因為TCP是流式數(shù)據(jù)格式,所以其解決思路還是從收到的數(shù)據(jù)中把包與包的邊界區(qū)分出來。
如何區(qū)分呢?
一般有以下三種方法。
(1)固定包長的數(shù)據(jù)包。固定包長,即每個協(xié)議包的長度都是固定的。假如我們規(guī)定每個協(xié)議包的大小都是64字節(jié),每收滿64字節(jié),就取出來解析(如果不夠,就先存起來),則這種通信協(xié)議的格式簡單但靈活性差。如果包的內容長度小于指定的字節(jié)數(shù),對剩余的空間就需要填充特殊的信息,例如\0(如果不填充特殊的內容,那么如何區(qū)分包里面的正常內容與填充信息呢);如果包的內容超過指定的字節(jié)數(shù),又得分包分片,則需要增加額外的處理邏輯——在發(fā)送端進行分包分片,在接收端重新組裝包片。
(2)以指定的字符(串)為包的結束標志。這種協(xié)議包比較常見,即在字節(jié)流中遇到特殊的符號值時就認為到一個包的末尾了。例如 FTP 或 SMTP,在一個命令或者一段數(shù)據(jù)后面加上\r\n(即CRLF)表示一個包的結束。對端收到數(shù)據(jù)后,每遇到一個“\r\n”,就把之前的數(shù)據(jù)當作一個數(shù)據(jù)包。這種協(xié)議一般用于一些包含各種命令控制的應用中,其不足之處就是如果協(xié)議數(shù)據(jù)包的內容部分需要使用包結束標志字符,就需要對這些字符做轉碼或者轉義操作,以免被接收方錯誤地當成包結束標志而誤解析。
(3)包頭+包體格式。這種格式的包一般分為兩部分,即包頭和包體,包頭是固定大小的,且包頭必須包含一個字段來說明接下來的包體有多大。例如:
struct msg_header{int32_t bodySize;int32_t cmd;};
就是一個典型的包頭格式,bodySize指定了這個包的包體是多大。
由于包頭的大小是固定的(這里是size(int32_t) + sizeof(int32_t) = 8字節(jié)),所以對端先收取包頭大小的字節(jié)內容(當然,如果不夠,則還是將其先緩存起來,直到收夠為止),然后解析包頭,根據(jù)包頭中指定的包體大小收取包體,等包體收夠了,就組裝成一個完整的包來處理。
在某些實現(xiàn)中,包頭中的bodySize可能被另一個叫作packageSize的字段代替,這個字段用于表示整個包的大小(即包頭加上包體的大小),這時,我們只要用packageSize減去包頭大小(這里是sizeof(msg_header))就能算出包體的大小,原理同上。

在使用大多數(shù)網(wǎng)絡庫時,我們通常需要根據(jù)協(xié)議的格式自己對數(shù)據(jù)包分界和解析,一般的網(wǎng)絡庫不提供這種功能是因為需要支持不同的協(xié)議。
由于協(xié)議的不確定性,網(wǎng)絡庫無法預先提供具體的解包代碼。當然,這不是絕對的,也有一些網(wǎng)絡庫提供了這種功能。
在Java Netty網(wǎng)絡框架中提供了FixedLengthFrameDecoder類處理長度是定長的協(xié)議包,提供了DelimiterBasedFrameDecoder類處理將特殊字符作為結束符的協(xié)議包,提供了ByteToMessageDecoder類處理自定義格式的協(xié)議包(可用來處理包頭+包體這種格式的數(shù)據(jù)包)。
然而,在繼承ByteToMessageDecoder的子類中,我們需要根據(jù)自己的協(xié)議的具體格式重寫decode方法對數(shù)據(jù)包進行解包。
本文摘自《C++服務器開發(fā)精髓》一書!


▊《C++服務器開發(fā)精髓》
張遠龍 著
從操作系統(tǒng)原理角度講解C++服務器開發(fā)技術棧
內容詳盡細致、版本新
重磅級C++服務器開發(fā)紅寶書
本書詳細講解如何掌握C++服務器開發(fā)技術,以及如何成為合格的C++開發(fā)者,秉承的思想是,通過掌握技術原理,可以輕松制造“輪子”,靈活設計出優(yōu)雅、魯棒的服務,并快速學習新技術。
無論是對于C/C++開發(fā)者、計算機專業(yè)的學生,還是對于想了解操作系統(tǒng)原理的讀者,本書都極具參考價值。
(掃碼了解本書詳情)
如果喜歡本文 歡迎 在看丨留言丨分享至朋友圈 三連 熱文推薦
▼點擊閱讀原文,查看本書詳情~
