?本文圍繞 TCP? 協(xié)議展開,先來回顧下 TCP 協(xié)議的特點:
TCP 是面向連接的傳輸層協(xié)議。每一條TCP? 連接只有兩個端點,每一條TCP 連接只能是點對點的(一對一)。TCP 提供可靠的交付服務,保證傳輸?shù)臄?shù)據(jù)無差錯、不丟失、不重復且有序。TCP? 提供全雙工通信,TCP? 允許通信雙方的應用進程在任何時候都能發(fā)送數(shù)據(jù),為此TCP 連接的兩端都設有發(fā)送緩存和接收緩存,用來臨時存放雙向通信的數(shù)據(jù)。TCP?是面向字節(jié)流的。(本文重點)雖然應用程序和 TCP? 的交互是一次一個數(shù)據(jù)塊(大小不等),但 TCP? 把應用程序交下來的數(shù)據(jù)看成僅僅是一連串的無結(jié)構(gòu)的字節(jié)流。
粘包警察由來?粘包由來?粘包警察 ,一詞首次看到是在 v2?。粘包警察認為 “粘包” 這詞侮辱了 TCP?,在 TCP? 下討論 “粘包” 是偽命題。相反,粘包學家認為 “粘包” 就是 TCP? 問題。遂粘包警察頻頻現(xiàn)身『TCP?粘包』帖子下,試圖改正這偏見,提醒各位: TCP 是面向字節(jié)流的。
(資料圖片僅供參考)
粘包由來小故事:
什么是粘包/拆包?據(jù)說以前有一群基礎不扎實的程序員經(jīng)常使用 VC? 寫各種 Windows? 客戶端程序,喜歡使用 UDP? 編程(VC? 的 UDP? 編程,代碼簡單,收發(fā)邏輯簡單明)。 因為通訊應用的復雜性以及需求需要,他們嘗試將多條數(shù)據(jù)放在一個 UDP? 數(shù)據(jù)包里進行發(fā)送,遂碰到『粘包問題』。同時他們開始接觸并使用 TCP?,慣性思維套用之前 UDP? 編程方式來使用 TCP?,非常容易遇到所謂的 『粘包問題』。隨著硬件升級,多物理核的 CPU 普及,多線程與并行編程開始流程,對程序員基本功提出更高的要求,這群人仍在并行程序使用串行思維進行編程,必定遇到『粘包問題』。 于是這群人把這個問題總結(jié)出來,稱之為 『粘包問題』。
所謂粘包: 就是幾個數(shù)據(jù)包粘在一起了,如果要處理得先拆包。
所謂拆包: 就是收到一批數(shù)據(jù)包碎片,要把這些碎片粘起來才能合成一個完整的數(shù)據(jù)包。
舉個栗子:客戶端發(fā)送數(shù)據(jù)給服務端,可能會出現(xiàn)以下五種情況:
栗子一:客戶端分別發(fā)送完整的數(shù)據(jù)包 A 和 B,服務端先接收了完整數(shù)據(jù)包 A,沒有出現(xiàn)拆包/粘包問題。栗子二:客戶端一次一口次發(fā)送 A 和 B 粘在一起的數(shù)據(jù)包,服務端接收到這個數(shù)據(jù)包,服務端需要解析出 A 和 B,出現(xiàn)粘包問題。栗子三:客戶端發(fā)送A|B-1?數(shù)據(jù)包和B-2數(shù)據(jù)包,服務端先接收到完整的 A 和 B 的一部分數(shù)據(jù)包 B-1,服務端需要解析出完整的 A,并等待讀取完整的 B 數(shù)據(jù)包,出現(xiàn)粘包/拆包問題。栗子四:客戶端發(fā)送A-1?數(shù)據(jù)包和B|A-2數(shù)據(jù)包,服務端接收到 A 的一部分數(shù)據(jù)包 A-1,此時需要等待接收到完整的 A 數(shù)據(jù)包,出現(xiàn)拆包問題。栗子五:數(shù)據(jù)包 A 較大,客戶端分段發(fā)送數(shù)據(jù)包A,服務端需要多次才可以接收完數(shù)據(jù)包 A,出現(xiàn)拆包問題。小結(jié): 由于拆包/粘包問題的存在,如何識別一個完整的數(shù)據(jù)包就成了問題?難點在于如何定義一個數(shù)據(jù)包的邊界。
為什么會有人說 TCP 粘包?先來看下應用程序使用 TCP? 套接字的流程: 對應 TCP/IP 4層協(xié)議:
應用進程調(diào)用write 時,內(nèi)核從該應用進程的緩沖區(qū)中復制所有數(shù)據(jù)到所寫套接字的發(fā)送緩沖區(qū)。本端TCP? 以MSS? 大小的或更小的塊把數(shù)據(jù)傳遞給IP。TCP?分段加上IP? 首部構(gòu)成 IP? 數(shù)據(jù)包,并按照其目的IP 地址查找路由表項以確定外出接口,然后把數(shù)據(jù)報傳遞給相應的數(shù)據(jù)鏈路。這里解釋下 MSS? 和 MTU:
MTU(Maxitum Transmission Unit?) 是鏈路層一次最大傳輸數(shù)據(jù)的大小。一般來說大小為 1500byte。MSS(Maximum Segement Size?) 是指TCP 最大報文段長度,它是傳輸層一次發(fā)送最大數(shù)據(jù)的大小。MTU? 和 MSS? 一般的計算關系為:MSS? = MTU? - IP? 首部 - TCP首部。
『粘包學家』認為 TCP 粘包/拆包發(fā)生原因有三:
應用程序write 寫入的字節(jié)大小大于套接字發(fā)送緩沖區(qū)大小。MSS? +TCP? 首部 +IP? 首部 >MTU?,就要 TCP 分段。以太網(wǎng)幀的payload? 大于MTU? 就要進行 IP 分片。說白了,『粘包學家』認為我怎么給你的,你就該怎么還給我。
『粘包警察』認為這根本不是 TCP 的鍋:
TCP 是面向字節(jié)流:根本沒有包這個概念,談何粘包/拆包?!赫嘲?拆包』本質(zhì)問題在于:如何從二進制流中提取數(shù)據(jù),如何定義數(shù)據(jù)的邊界。說白了,『粘包警察』認為怎么解析數(shù)據(jù)是你應用層的問題,TCP 只管傳輸并提供可靠的交付服務。
拓展:Nagle 算法Nagle? 算法于 1984 年被福特航空和通信公司定義為 TCP/IP? 擁塞控制方法,這使福特經(jīng)營的最早的專用 TCP/IP 網(wǎng)絡減少擁塞控制,從那以后這一方法得到了廣泛應用。
優(yōu)勢:為了盡可能發(fā)送大塊數(shù)據(jù),避免網(wǎng)絡中充斥著許多小數(shù)據(jù)塊。
如果每次需要發(fā)送的數(shù)據(jù)只有 1 字節(jié),加上 20 個字節(jié)的 IP?首部 和 20 個字節(jié)的 TCP首部,每次發(fā)送的數(shù)據(jù)包大小為 41 字節(jié),但是只有 1 字節(jié)是有效信息,這就造成了非常大的浪費。
Nagle? 算法的規(guī)則(可參考tcp_output.c? 文件里 tcp_nagle_check 函數(shù)注釋):
如果包長度達到MSS,則允許發(fā)送;如果該包含有FIN,則允許發(fā)送;設置了TCP_NODELAY 選項,則允許發(fā)送;未設置TCP_CORK? 選項時,若所有發(fā)出去的小數(shù)據(jù)包(包長度小于MSS)均被確認,則允許發(fā)送;上述條件都未滿足,但發(fā)生了超時(一般為200ms),則立即發(fā)送。Linux? 在默認情況下是開啟 Nagle 算法的,在大量小數(shù)據(jù)包的場景下可以有效地降低網(wǎng)絡開銷。
可以通過Linux? 提供的TCP_NODELAY? 參數(shù)禁用Nagle 算法。Netty? 中為了使數(shù)據(jù)傳輸延遲最小化,就默認禁用了Nagle 算法。Tips:? 還有一個延遲 ACK(Delay ACK?),TCP? 何時發(fā)送 ACK 有如下規(guī)定:
當有響應數(shù)據(jù)發(fā)送的時候,ACK 會隨著數(shù)據(jù)一塊發(fā)送。.如果沒有響應數(shù)據(jù),ACK 就會有一個延遲,以等待是否有響應數(shù)據(jù)一塊發(fā)送,但是這個延遲一般在40ms~500ms之間,一般情況下在40ms左右。如果在等待發(fā)送ACK? 期間,第二個數(shù)據(jù)又到了,這時候就要立即發(fā)送ACK。拓展:UDP 為什么不分段?先來回顧下 UDP 的特點:
UDP 無需建立連接。無連接狀態(tài)。分組首部開銷小。(首部 8字節(jié))UDP? 是面向報文的。(重點)發(fā)送方 UDP? 對應用層交下來的報文,在添加首部后就向下交付給 IP? 層,既不合并,也不拆分,而是保留這些報文的邊界; 接收方 UDP? 對 IP? 層交上來 UDP? 用戶數(shù)據(jù)報,在去除首部后就原封不動地交付給上層應用進程,一次交付一個完整的報文。因此報文不可分割,是 UDP? 數(shù)據(jù)報處理的最小單位。
再看 UDP 數(shù)據(jù)報格式:
可知一個 UDP? 數(shù)據(jù)報可攜帶最大用戶數(shù)據(jù)長度為:2^16 - 8 = 65535 - 8 = 65527 (B)
小結(jié)下 UDP 為什么不分段?
UDP? 協(xié)議特性:面向報文。16位UDP 長度。沒有分段的能力:標記分段先后順序的能力,即編號(ID?)、尾部編號的標識 (Flag)UDP? 應用特性:常用于一次性傳輸比較少量數(shù)據(jù)的網(wǎng)絡應用,如DNS、SNMP? 等。當 DNS? 查詢超過 512字節(jié) 時,協(xié)議的 TC? 標志出現(xiàn)刪除標志,這時則使用 TCP? 發(fā)送。通常傳統(tǒng)的 UDP? 報文一般不會大于512字節(jié)。
拆包/粘包解決方案由上文可知我們需要一種定義來數(shù)據(jù)包的邊界,這也是解決拆包/粘包的唯一方法:定義應用層的通信協(xié)議。
主流協(xié)議解決方案有:
消息長度固定特定分隔符消息長度 + 消息內(nèi)容Netty 對三種常用封幀方式的支持:
方式 | 解碼 | 編碼 |
固定長度 | ? | 簡單 |
分隔符 | ? | 簡單 |
固定長度字段存內(nèi)容長度 | ? | ? |
Netty? 中提供了類 FixedLengthFrameDecoder:
每個數(shù)據(jù)報文都需要一個固定的長度。當接收方累計讀取到固定長度的報文后,就認為已經(jīng)獲得一個完整的消息。當發(fā)送方的數(shù)據(jù)小于固定長度時,則需要空位補齊。# 舉個栗子:假定固定消息長度是 3字節(jié),當你收到如下報文:+---+----+------+----+| A | BC | DEFG | HI |+---+----+------+----+# 將它們解碼成以下 3個固定長度的數(shù)據(jù)包:+-----+-----+-----+| ABC | DEF | GHI |+-----+-----+-----+
項目地址:對應代碼:
ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer 通過 telnet? 去訪問:telnet localhost 8088 優(yōu)缺點: 既然接收方無法區(qū)分消息的邊界,那么可以在每次發(fā)送報文的尾部加上特定分隔符,接收方就可以根據(jù)特殊分隔符進行消息拆分。
DelimiterBasedFrameDecoder 自動完成以分隔符做結(jié)束標志的消息的解碼:
# 舉個栗子:以下報文根據(jù)特定分隔符 `\n` 按行解析+--------------+| ABC\nDEF\r\n |+--------------+# 解析后得到:+-----+-----+| ABC | DEF |+-----+-----+
項目地址:代碼
ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer 通過 telnet? 去訪問:telnet localhost 8088 比較推薦的做法是:將消息進行編碼,例如 base64 編碼,然后可以選擇 64 個編碼字符之外的字符作為特定分隔符。 特定分隔符法在消息協(xié)議足夠簡單的場景下比較高效,Redis 在通信過程中采用的就是換行分隔符。 消息長度 + 消息內(nèi)容是項目開發(fā)中最常用的一種協(xié)議,如下展示了該協(xié)議的基本格式。 +--------|----------+|消息頭 |消息體 |+--------|----------+| Length | Content |+--------|----------+ 消息頭中存放消息的總長度,例如使用 4 字節(jié)的 int 值記錄消息的長度,消息體實際的二進制的字節(jié)數(shù)據(jù)。 接收方在解析數(shù)據(jù)時: 依然以上述提到的原始字節(jié)數(shù)據(jù)為例,使用該協(xié)議進行編碼后的結(jié)果如下所示: +-----|-------|-------|----|-----+| 2AB | 4CDEF | 4GHIJ | 1K | 2LM |+-----|-------|-------|----|-----+ 消息長度 + 消息內(nèi)容的使用方式非常靈活,且不會存在消息定長法和特定分隔符法的明顯缺陷。 當然在消息頭中不僅只限于存放消息的長度,而且可以自定義其他必要的擴展字段: 關鍵詞:
應用進程
應用程序
字節(jié)數(shù)據(jù)
解決方案
一般情況下
X 關閉
X 關閉
- 15G資費不大降!三大運營商誰提供的5G網(wǎng)速最快?中國信通院給出答案
- 2聯(lián)想拯救者Y70發(fā)布最新預告:售價2970元起 迄今最便宜的驍龍8+旗艦
- 3亞馬遜開始大規(guī)模推廣掌紋支付技術(shù) 顧客可使用“揮手付”結(jié)賬
- 4現(xiàn)代和起亞上半年出口20萬輛新能源汽車同比增長30.6%
- 5如何讓居民5分鐘使用到各種設施?沙特“線性城市”來了
- 6AMD實現(xiàn)連續(xù)8個季度的增長 季度營收首次突破60億美元利潤更是翻倍
- 7轉(zhuǎn)轉(zhuǎn)集團發(fā)布2022年二季度手機行情報告:二手市場“飄香”
- 8充電寶100Wh等于多少毫安?鐵路旅客禁止、限制攜帶和托運物品目錄
- 9好消息!京東與騰訊續(xù)簽三年戰(zhàn)略合作協(xié)議 加強技術(shù)創(chuàng)新與供應鏈服務
- 10名創(chuàng)優(yōu)品擬通過香港IPO全球發(fā)售4100萬股 全球發(fā)售所得款項有什么用處?