天天看點

TCP 粘包拆包

粘包問題

在 TCP 這種位元組流協定上做

應用層分包

是網絡程式設計的基本需求。分包指的是在發生一個消息(message)或一幀(frame)資料時,通過一定的處理,讓接收方能從位元組流中識别并截取(還原)出一個個消息。是以,

“粘包問題”是個僞命題

短連接配接分包

對于短連接配接的 TCP 服務,分包不是一個問題,隻要發送方主動關閉連接配接,就表示一個消息發送完畢,接收方 read() 傳回0,進而知道消息的結尾

TCP 發送機制

為了提高 TCP 的傳輸效率,TCP 有一套自己的發送機制

  • TCP 維持一個變量,它等于

    最大封包段長度 MSS

    。隻要緩存中存放的資料達到 MSS 位元組時,就組裝成一個 TCP 封包段發送出去
  • 由發送方的應用程序指明要求發送封包段,即 TCP 支援的

    推送(push)

    操作
  • 發送方的一個計時器期限到了,這時把目前已有的緩存資料裝入封包段(但長度不能超過 MSS)發送出去

長連接配接分包

對于長連接配接的 TCP 服務,分包有四種方法

  • 消息長度固定
  • 使用特殊的字元或字元串作為消息的邊界,例如 HTTP 協定的 headers 以“rn”為字段的分隔符
  • 在每條消息的頭部加一個長度字段,這恐怕是最常見的做法
  • 利用消息本身的格式來分包,例如 XML 格式的消息中

    <root>

    ...

    </root>

    的配對,或者 JSON 格式中的 { ... } 的配對。解析這種消息格式通常會用到狀态機(state machine)

複雜的分包

假如消息格式非常簡單,“消息”本身是一個字元串,每條消息有一個4位元組的頭部,以網絡序存放字元串的長度。消息直接沒有間隙,字元串也不要求以 '0' 結尾

發送兩條消息“hello”和“smartboy”,打包後的位元組流共有21位元組

0x00, 0x00, 0x00, 0x05, 'h', 'e', 'l', 'l', 'o',
0x00, 0x00, 0x00, 0x08, 's', 'm', 'a', 'r', 't', 'b', 'o', 'y'           

假設資料最終都全部到達,資料解析邏輯至少能正确處理以下各種資料到達的次序

  • 一個位元組一個位元組到達
  • 資料分兩次到達,第一次收到2個位元組,不足消息的長度字段
  • 資料分兩次到達,第一次收到4個位元組,剛好夠長度字段,但是沒有 body
  • 資料分兩次到達,第一次收到8個位元組,長度完整,但 body 不完整
  • 資料分兩次到達,第一次收到9個位元組,長度完整,但 body 也完整
  • 資料分兩次到達,第一次收到10個位元組,第一條消息的長度完整、body 也完整,第二條消息長度不完整
  • 請自行移動和增加分割點,一共有超過 100 萬種可能(221-1)
  • 資料一次就全部到達

《TCP粘包拆包》 原文連結:

https://blog.maplemark.cn/2019/04/tcp粘包拆包.html?utm=alc