Linux 網路程式設計——TCP 粘包及其解決方案
首先,我們回顧一下 TCP 和 UDP 的頭部資訊:
我們知道,TCP 和 UDP 是 TCP/IP 協議族傳輸層中的兩個具有代表性的協議。其中,TCP 是面向連線的複雜的、可靠的位元組流傳輸協議,而 UDP 是面向無連線的簡單的、不可靠的資料報傳輸協議。
“流”的概念就是指不間斷的資料結構,可以把它想象成你們家裡的自來水管道中的水流。什麼意思呢?舉個例子:TCP 傳送端應用程式傳送了10次100位元組的訊息,那麼,在接收端的應用程式接收到的可能是一個1000位元組的連續不間斷的資料。但如果是 UDP 埠傳送了一個100位元組的訊息,那麼 UDP 接收端就會以100位元組的長度來接收資料。正因為這樣,我們可以看到 TCP 頭部中並沒有長度資訊,而 UDP 頭部包含長度資訊。好了,現在你應該明白我們在建立套接字的時候,為什麼 type 的型別是 SOCK_STREAM
正如文章標題所述,顯然,你已經知道 UDP 資料包存在明確邊界,是不存在粘包現象的,只有 TCP 才會出現粘包現象。那麼,接下來我們逐步分析 TCP 的流傳輸特性所帶來如粘包這樣的一些問題及其解決方案。
按每次通訊後是否閉關連線,可以分為兩類情況:長連線和短連線。長連線——指的是客戶端和服務端先建立通訊連線,連線建立後不斷開,然後再進行報文傳送和接收。短連線——指的是客戶端和服務端每進行一次報文收發交易時才進行通訊連線,交易完畢後立即斷開連線,比如 http 協議。
所以我們可以分析以下幾種情況:
(1)如果利用 TCP 每次傳送資料,就與對方建立連線,然後雙方傳送完一段資料後,就關閉連線,這樣就不會出現粘包問題(因為只有一種資料結構)。
(2)如果傳送資料無結構,如檔案傳輸,這樣傳送端只管傳送,接收端只管接收儲存就行,也不用考慮粘包。
(3)如果雙方建立連線,需要在連線後一段時間內傳送多個不同結構的資料,這時候接收端收到就可能是一堆粘在一起的資料,這樣接收端應用程式就傻了,到底是要幹嘛?不知道,因為協議並沒有規定這麼詭異的資料。
那麼,可以認為 TCP 粘包問題並不是對所有應用都造成困擾的,只是對那些長連線並且需要傳輸多種資料結構的應用造成影響。仔細分析會發現,除了粘包問題,其實還可能會出現多包、少包、半包、斷包等情況。
針對這些問題,一般會有如下解決方法:
(1)呼叫傳送函式之後都強制資料立即傳送(PUSH 指令)。
(2)對於接收端引起的粘包,則可通過優化程式設計、精簡接收程序工作量、提高接收程序優先順序等措施,使其及時接收資料,從而儘量避免出現粘包現象。
(3)新增一個固定的訊息頭,該訊息頭包含資料長度資訊,每次資料時先接收固定大小的訊息頭,再根據其攜帶的長度資訊接收訊息實體。也就是說,通過人為控制多次接收來避免粘包。
(4)設定 TCP_NODELAY 選項,禁止 Nagle 演算法。
(5)設定 SO_RCVBUF 和 SO_SNDBUF 選項,根據應用需求修改一個合適的接收、傳送緩衝區大小。
(6)新增報文分隔標識,比如傳送報文是在末尾新增 '\n'
實際上,上述的幾種解決方法都有不足之處,並且不一定能夠完全避免 TCP 粘包問題。所以還是需要根據實際應用來進行應用場景和效能方面的衡量。