在线乱码卡一卡二卡新HD,最近韩国免费观看视频,国产色无码精品视频国产,亚洲男人的天堂久久香蕉

熱消息:TCP 粘包?粘包警察是什么梗?
來源:稀土掘金    時間:2022-09-06 10:03:06

?本文圍繞 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 對三種常用封幀方式的支持:

方式

解碼

編碼

固定長度

??FixedLengthFrameDecoder??

簡單

分隔符

??DelimiterBasedFrameDecoder??

簡單

固定長度字段存內(nèi)容長度

??LengthFieldBasedFrameDecoder??

??LengthFieldPrepender??

固定消息長度

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() { @Override public void initChannel(SocketChannel ch) { ch.pipeline().addLast(new FixedLengthFrameDecoder(3)); //... ... } });

通過 telnet? 去訪問:telnet localhost 8088

優(yōu)缺點:

優(yōu)點:消息定長法使用非常簡單缺點:無法很好設定固定長度的值,如果長度太大會造成字節(jié)浪費,長度太小又會影響消息傳輸,所以在一般情況下消息定長法不會被采用。特殊分隔符

既然接收方無法區(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() { @Override public void initChannel(SocketChannel ch) { // 以 & 為分隔符 ByteBuf delimiter = Unpooled.copiedBuffer("&".getBytes()); // 10 表示單條消息的最大長度,當達到該長度后扔沒有查找到分隔符,就拋出異常 // TooLongFrameException,防止由于異常碼流失分隔符導致的內(nèi)存溢出 ch.pipeline().addLast(new DelimiterBasedFrameDecoder(10, delimiter)); // ... ... } });

通過 telnet? 去訪問:telnet localhost 8088

比較推薦的做法是:將消息進行編碼,例如 base64 編碼,然后可以選擇 64 個編碼字符之外的字符作為特定分隔符。

特定分隔符法在消息協(xié)議足夠簡單的場景下比較高效,Redis 在通信過程中采用的就是換行分隔符。

Redis 2.0? 以后的通信統(tǒng)一為RESP? 協(xié)議(Redis Serialization Protocol)RESP? 是一個二進制安全的文本協(xié)議,工作于TCP? 協(xié)議上。RESP? 以行作為單位,客戶端和服務器發(fā)送的命令或數(shù)據(jù)一律以\r\n(CRLF)作為換行符。消息長度 + 消息內(nèi)容

消息長度 + 消息內(nèi)容是項目開發(fā)中最常用的一種協(xié)議,如下展示了該協(xié)議的基本格式。

+--------|----------+|消息頭 |消息體 |+--------|----------+| Length | Content |+--------|----------+

消息頭中存放消息的總長度,例如使用 4 字節(jié)的 int 值記錄消息的長度,消息體實際的二進制的字節(jié)數(shù)據(jù)。

接收方在解析數(shù)據(jù)時:

首先讀取消息頭的長度字段Len然后緊接著讀取長度為Len 的字節(jié)數(shù)據(jù),該數(shù)據(jù)即判定為一個完整的數(shù)據(jù)報文

依然以上述提到的原始字節(jié)數(shù)據(jù)為例,使用該協(xié)議進行編碼后的結(jié)果如下所示:

+-----|-------|-------|----|-----+| 2AB | 4CDEF | 4GHIJ | 1K | 2LM |+-----|-------|-------|----|-----+

消息長度 + 消息內(nèi)容的使用方式非常靈活,且不會存在消息定長法和特定分隔符法的明顯缺陷。

當然在消息頭中不僅只限于存放消息的長度,而且可以自定義其他必要的擴展字段:

消息版本算法類型等等

關鍵詞: 應用進程 應用程序 字節(jié)數(shù)據(jù) 解決方案 一般情況下

上一篇:

下一篇:

X 關閉

X 關閉

<蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>