自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

為什么 TCP 粘包是正?,F(xiàn)象

網(wǎng)絡(luò)
TCP 粘包 并不是因?yàn)閰f(xié)議本身有 “問題”,而是一種 “正?,F(xiàn)象”, 因?yàn)?TCP 是面向字節(jié)流的協(xié)議,數(shù)據(jù)之間沒有所謂 “邊界”,所以數(shù)據(jù)粘包之后的 “拆包” 工作應(yīng)該 (也必須) 由應(yīng)用層完成。

一、TCP 粘包現(xiàn)象

TCP 粘包是指發(fā)送方發(fā)送了若干數(shù)據(jù)包,但是到達(dá)接收方之后,數(shù)據(jù)包的內(nèi)容全部 “粘連” 在了一起,每個(gè)數(shù)據(jù)包的數(shù)據(jù)結(jié)尾直接和下個(gè)數(shù)據(jù)包的數(shù)據(jù)連在了一起,無法正常區(qū)分。

下面的圖片展示了一個(gè) TCP 粘包現(xiàn)象,正常的 3 個(gè)數(shù)據(jù)包因?yàn)檎嘲?,被連在了一起,看起來像是一個(gè)數(shù)據(jù)包,結(jié)果就很感人了 ...

二、TCP 粘包/拆包 原因


TCP 粘包 并不是因?yàn)閰f(xié)議本身有 “問題”,而是一種 “正常現(xiàn)象”, 因?yàn)?TCP 是面向字節(jié)流的協(xié)議,數(shù)據(jù)之間沒有所謂 “邊界”,所以數(shù)據(jù)粘包之后的 “拆包” 工作應(yīng)該 (也必須) 由應(yīng)用層完成。


TCP 采用 “異步” 方式發(fā)送應(yīng)用數(shù)據(jù),也就是說,當(dāng)應(yīng)用程序中調(diào)用 Send(packet) 發(fā)送數(shù)據(jù)時(shí),雖然 Send 函數(shù)會(huì)立即返回,但是數(shù)據(jù)并不一定已經(jīng)到達(dá)通信對(duì)方了。應(yīng)用數(shù)據(jù)具體什么時(shí)候發(fā),由應(yīng)用層下面的傳輸層 TCP 說了算,TCP 使用了 3 個(gè)主要機(jī)制 (確認(rèn)與重傳、滑動(dòng)窗口流量控制、擁塞控制) 來實(shí)現(xiàn)可靠性傳輸,保證應(yīng)用數(shù)據(jù)的可靠傳輸和 應(yīng)用層數(shù)據(jù)發(fā)送順序和到達(dá)順序一致的語(yǔ)義保證。

下面來展開說一下可能導(dǎo)致 粘包/拆包 問題的原因。

1. 面向字節(jié)流的工作特性

TCP 作為傳輸層,并不了解 (也不關(guān)心) 應(yīng)用層數(shù)據(jù)的上下文含義,它只會(huì)根據(jù)通信雙方約定的 MSS[1] 對(duì)發(fā)送緩沖區(qū)的數(shù)據(jù)包進(jìn)行拆分。

所以在應(yīng)用層的視角來看,一個(gè)完整的數(shù)據(jù)包 (可能是一段聊天文字、一個(gè)圖片、一個(gè)視頻) 可能會(huì)經(jīng)歷不同的發(fā)送過程:

  • 如果 MSS 較小,一個(gè)完整的數(shù)據(jù)包會(huì)被 TCP 拆分成多個(gè)更小的 數(shù)據(jù)包進(jìn)行發(fā)送,產(chǎn)生拆包現(xiàn)象
  • 如果 MSS 較大,多個(gè)完整的數(shù)據(jù)包會(huì)被 TCP 合并為一個(gè)更大的 數(shù)據(jù)包進(jìn)行發(fā)送,產(chǎn)生粘包現(xiàn)象

2. 緩沖機(jī)制

TCP 在發(fā)送數(shù)據(jù)時(shí),會(huì)將數(shù)據(jù)放入發(fā)送緩沖區(qū);在接收數(shù)據(jù)時(shí),會(huì)將數(shù)據(jù)放入接收緩沖區(qū)。TCP 會(huì)盡可能地將發(fā)送緩沖區(qū)中的數(shù)據(jù)打包成一個(gè)或多個(gè)數(shù)據(jù)包發(fā)送出去,而接收方在讀取數(shù)據(jù)時(shí),也會(huì)盡量將接收緩沖區(qū)中的數(shù)據(jù)全部讀取出來。這種機(jī)制可能導(dǎo)致發(fā)送方一次發(fā)送的多個(gè)數(shù)據(jù)包被接收方一次性讀取,從而引發(fā)粘包問題。

  • TCP 發(fā)送方會(huì)將數(shù)據(jù)放入 發(fā)送緩沖區(qū)
  • TCP 接收方會(huì)將數(shù)據(jù)放入 接收緩沖區(qū)

為了盡可能提升發(fā)送數(shù)據(jù)和接受處理數(shù)據(jù)的性能,作為發(fā)送方來講:

  • 如果要發(fā)送的數(shù)據(jù)小于發(fā)送緩沖區(qū)大小,TCP 會(huì)將多次要發(fā)送的數(shù)據(jù),全部寫入發(fā)送緩沖區(qū),然后一起發(fā)送,產(chǎn)生粘包現(xiàn)象
  • 如果要發(fā)送的數(shù)據(jù)大于發(fā)送緩沖區(qū)大小,TCP 會(huì)將要發(fā)送的數(shù)據(jù)進(jìn)行切分,滿足寫入發(fā)送緩沖區(qū)的條件,然后發(fā)送,產(chǎn)生拆包現(xiàn)象

作為接收方來講:

  • 如果應(yīng)用層沒有及時(shí)處理接收緩沖區(qū)的數(shù)據(jù),產(chǎn)生粘包現(xiàn)象

3. Nagle 算法

Nagle 算法原理: 發(fā)送方已經(jīng)發(fā)送數(shù)據(jù)還未被接收方確認(rèn)之前,期間如果又有小數(shù)據(jù)生成,先把小數(shù)據(jù)收集起來,湊滿一個(gè) MSS (最大報(bào)文段大小) 或者收到接收方 Ack 后再一起發(fā)送。通過將小數(shù)據(jù)包積累成較大的數(shù)據(jù)包后再發(fā)送,從而提高網(wǎng)絡(luò)效率。

很顯然,根據(jù) Nagle 算法的工作機(jī)制,在頻繁發(fā)送小的數(shù)據(jù)包時(shí) (例如 Telnet, SSH 終端),會(huì)產(chǎn)生粘包現(xiàn)象。

下面是在 Go 語(yǔ)言中關(guān)閉 TCP Nagle 算法的示例代碼。

package main

func main() {
    conn, _ := net.Dial("tcp", "dbwu.tech:443")

    fd := conn.(*net.TCPConn).File()
    // 關(guān)閉 Nagle 算法
    syscall.SetsockoptInt(int(fd.Fd()), syscall.IPPROTO_TCP, syscall.TCP_NODELAY, 1)
}

? 單純關(guān)閉 Nagle 算法并不能解決粘包問題,讀者思考一下為什么?

?? 綜上所述,因?yàn)閼?yīng)用層和傳輸層工作方式上的差異,所以就導(dǎo)致了所以的 “TCP 粘包/拆包” 問題。

三、解決方案

既然拆包必須由應(yīng)用層來完成,那么按照數(shù)據(jù)處理的思路,只要應(yīng)用程序?qū)?TCP 的字節(jié)流數(shù)據(jù)能夠區(qū)分單個(gè)消息的標(biāo)志和邊界,那么應(yīng)用程序就可以將粘包后的數(shù)據(jù)分割為正常的單個(gè)消息,粘包問題自然迎刃而解。

目前業(yè)界主要采用的解決方案:

(1) 設(shè)置消息固定長(zhǎng)度: 發(fā)送方可以將每個(gè)消息設(shè)置為固定的長(zhǎng)度 (對(duì)于長(zhǎng)度不夠的消息可以使用 0 進(jìn)行填充),接收方從緩沖區(qū)讀取數(shù)據(jù)時(shí),每次都讀取固定的長(zhǎng)度,這樣很自然就把單個(gè)消息拆分出來

(2) 設(shè)置消息分隔符: 發(fā)送方在將單個(gè)消息末尾追加分隔符,用來分區(qū)單個(gè)消息,接收方從緩沖區(qū)讀取數(shù)據(jù)后,根據(jù)分隔符將讀取到的數(shù)據(jù)切割成一個(gè)個(gè)單條消息

當(dāng)然,分隔符很容易出現(xiàn)在要發(fā)送的應(yīng)用數(shù)據(jù)中,這樣就產(chǎn)生了沖突,無法進(jìn)行正常拆包,所以實(shí)際項(xiàng)目中很少使用這種方式。

(3) 設(shè)置消息頭部格式: 將消息分為消息頭部和消息體,消息頭中包含表示消息總長(zhǎng)度(或者消息體長(zhǎng)度,例如 HTTP 中的 Content-Length)字段,接收方首先讀取消息頭部,然后根據(jù)消息長(zhǎng)度字段 讀取具體的消息體內(nèi)容,這也是最常用的方式

(4) 特定消息格式: 例如將單個(gè)消息固定為 JSON 格式,接收方從緩沖區(qū)讀取數(shù)據(jù)后,根據(jù)讀取到的數(shù)據(jù)能否被解析成合法的 JSON 來判斷消息是否結(jié)束,當(dāng)然,實(shí)際項(xiàng)目中很少使用這種方式

??注意,以上提到的解決方案通常由網(wǎng)絡(luò)編程框架 (例如 Java 的 Netty, Golang 的 gnet) 來實(shí)現(xiàn),而不是由應(yīng)用程序中的業(yè)務(wù)代碼來實(shí)現(xiàn)。

四、不同應(yīng)用場(chǎng)景的下的應(yīng)對(duì)方案

完全禁止 粘包/拆包 的場(chǎng)景:

  • 低延遲要求: 在線游戲、股票交易
  • 小數(shù)據(jù)頻繁傳輸,例如 Telnet, SSH 終端

可以忽視 粘包/拆包 的場(chǎng)景:

  • 小數(shù)據(jù)傳輸,且兩次傳輸之間的時(shí)間間隔很大,例如 5 秒/次 的心跳
  • 使用已經(jīng)處理了粘包問題的應(yīng)用層協(xié)議,例如 HTTP 使用響應(yīng)頭中的 Content-Length 作為消息分隔符
  • 傳輸數(shù)據(jù)只需要保證順序即可,無需區(qū)分邊界,例如大文件傳輸,每個(gè)數(shù)據(jù)包都是文件的一小部分而已,因?yàn)?TCP 保證了傳輸順序性,所以接收方只需要讀取數(shù)據(jù),然后追加到已有數(shù)據(jù)的后面即可

五、UDP 有粘包/拆包 問題嗎?

UDP 是無連接的協(xié)議,每個(gè)數(shù)據(jù)報(bào)都是獨(dú)立傳輸?shù)?,接收方收到的?shù)據(jù)報(bào)和發(fā)送方發(fā)送的數(shù)據(jù)報(bào)是一一對(duì)應(yīng)的 (但是報(bào)文到達(dá)時(shí)間上可能會(huì)出現(xiàn)亂序),不需要建立連接,也不需要維護(hù)連接的狀態(tài),換句話說,選擇 UDP 協(xié)議時(shí),應(yīng)用層必須完成數(shù)據(jù)的拆包工作,所以自然也就不存在 粘包/拆包 問題了。

責(zé)任編輯:趙寧寧 來源: 洋芋編程
相關(guān)推薦

2020-03-10 08:27:24

TCP粘包網(wǎng)絡(luò)協(xié)議

2021-07-15 10:35:16

NettyTCPJava

2024-12-19 11:00:00

TCP網(wǎng)絡(luò)通信粘包

2019-10-17 11:06:32

TCP粘包通信協(xié)議

2022-08-01 07:07:15

粘包半包封裝

2021-03-09 22:30:47

TCP拆包協(xié)議

2019-10-24 07:35:13

TCP粘包Netty

2020-12-30 09:04:32

Go語(yǔ)言TCPUDP

2022-04-28 08:38:09

TCP協(xié)議解碼器

2020-12-23 07:53:01

TCP通信Netty

2024-08-16 21:47:18

2020-01-13 10:16:53

TCPUDP協(xié)議

2020-04-01 15:30:19

TCPUDP服務(wù)器

2020-01-15 08:42:16

TCP三次握手弱網(wǎng)絡(luò)

2020-01-06 15:23:41

NettyTCP粘包

2021-07-06 17:13:08

NVMe存儲(chǔ)協(xié)議數(shù)據(jù)中心

2022-07-27 07:36:01

TCP可靠性

2019-10-25 00:32:12

TCP粘包Netty

2019-09-30 09:41:04

五層協(xié)議OSITCP

2025-02-07 00:14:03

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)