Netty——TCP 粘包/拆包問題

文章目錄

  • 1. 什么是 粘包/拆包 問題?
  • 2. 原因
    • 2.1 Nagle 算法
    • 2.2 滑動窗口
    • 2.3 MSS 限制
    • 2.4 粘包的原因
    • 2.5 拆包的原因
  • 3. 解決方案
    • 3.1 固定長度消息
    • 3.2 分隔符標識
    • 3.3 長度前綴協議
      • 3.3.1 案例一
      • 3.3.2 案例二
      • 3.3.3 案例三
  • 4. 總結


1. 什么是 粘包/拆包 問題?

  • 粘包 (Sticky Packet):發送方連續發送的 多個獨立數據包,在接收方被合并成 一個數據包 接收,導致應用層無法區分原始消息的邊界。例如,發送方依次發送 A 和 B,接收方可能收到 AB。
  • 拆包 (Packet Splitting):發送方發送的 一個完整數據包,在傳輸過程中 被分割成多個小包,接收方需要 重新組裝 才能還原完整消息。例如,發送方發送 ABCD,接收方可能收到 AB 和 CD。

2. 原因

TCP 協議的設計目標是 高效傳輸字節流而非保證消息邊界。以下機制是導致問題的核心原因:

2.1 Nagle 算法

每個數據包都必須加上 TCP 頭 和 IP 頭,如果要傳遞的數據很少,那么這個數據包中大部分都是頭信息。如果將多個微小數據包合并成一個大數據包,那么網絡利用率就會提高。于是,為了減少網絡中 微小數據包 的數量,TCP 會將多個小數據包合并成一個大包發送,這就是 Nagle 算法。

2.2 滑動窗口

接收方為提高吞吐量,會采取以下兩個措施:

  • 延遲發送 ACK 以合并多個數據包的確認
  • 將收到的數據暫存到緩沖區,積累到一定量后再通知應用層讀取。從而導致應用層一次讀取多個數據包。

2.3 MSS 限制

鏈路層對一次能夠發送的最大數據有限制,這個限制稱之為 MTU (Maximum Transmission Unit),不同的鏈路設備的 MTU 值也有所不同,例如:

  • 以太網的 MTU 是 1500 字節。
  • 本地回環地址的 MTU 是 65535 字節 (本地測試不走網卡)。

MSS 是最大段長度 (Maximum Segment Size),它是 MTU 去除 TCP 頭和 IP 頭后剩余能夠作為數據傳輸的字節數。IPv4 TCP 頭占用 20 字節,IP 頭占用 20 字節,因此以太網 MSS 的值為 1500 - 40 = 1460 字節。TCP 在傳遞大量數據時,會按照 MSS 大小將數據進行分割發送。

2.4 粘包的原因

  • Nagle 算法:小數據包會被合并成大數據包,從而導致粘包。
  • 滑動窗口:假設 發送方 256 字節表示一個完整報文,但由于 接收方 處理不及時窗口大小足夠大,這 256 字節就會緩沖在 接收方 的滑動窗口中,當滑動窗口中緩沖了多個報文就會粘包。

2.5 拆包的原因

  • MSS 限制:當 發送的數據量超過 MSS 限制 后,會將數據切分發送,從而導致拆包。
  • 滑動窗口:假設 接收方 的窗口只剩 128 字節,發送方 的報文大小是 256 字節,這時窗口放不下這個報文,只能先發送前 128 字節,等待 ACK 后才能發送剩余部分,這就造成了拆包。

3. 解決方案

TCP 層無法感知消息邊界,因此需要應用層通過來解決,解決方案如下:

3.1 固定長度消息

思想:每條消息的長度固定,接收方按固定長度讀取

在 Netty 中的實現:將 FixedLengthFrameDecoder 作為 ChannelPipeline 的第一個處理器,如下所示:

// 添加一個 消息長度固定為 512 字節的解碼器
ch.pipeline().addLast(new FixedLengthFrameDecoder(512));

缺點消息長度不好把握,太短可能無法容納比較長的消息,太長可能會導致浪費。

3.2 分隔符標識

思想:在消息末尾添加特殊分隔符(如 \n),接收方通過解析分隔符分割消息

在 Netty 中的實現:將 LineBasedFrameDecoderDelimiterBasedFrameDecoder 作為 ChannelPipeline 的第一個處理器,如下所示:

  • 添加一個以換行符為特殊分隔符的解碼器:
    // 添加一個解碼器,它以 \n 或 \r\n 為分隔符分割消息
    // 但消息長度不能超過 1024 字節,如果超過,會拋出異常
    ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
    
  • 添加一個以指定字符串為特殊分隔符的解碼器:
    // 指定分隔符為 "EOM"
    ByteBuf delimiter = Unpooled.copiedBuffer("EOM".getBytes());
    // 添加一個解碼器,它以 "EOM" 為分隔符分割消息
    // 但消息長度不能超過 1024 字節,如果超過,會拋出異常
    ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
    

缺點分隔符不好確定,如果內容本身包含了分隔符,那么就會解析錯誤。

3.3 長度前綴協議

思想:在消息前添加固定長度的字段,表示消息總長度

在 Netty 中的實現:將 LengthFieldBasedFrameDecoder 作為 ChannelPipeline 的第一個處理器,如下所示:

ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024,  // 最大幀(消息)長度0,     // 長度字段偏移量4,     // 長度字段長度0,     // 長度調整值4      // 初始跳過字節數
));

LengthFieldBasedFrameDecoder 的重要參數:

  • maxFrameLength允許的最大幀長度。若接收到的消息長度超出這個值,解碼器會拋出 TooLongFrameException 異常,避免內存溢出。
  • lengthFieldOffset長度字段在消息中的偏移量,即從消息的哪個位置開始是長度字段。
  • lengthFieldLength長度字段本身的字節數
  • lengthAdjustment長度字段的值與實際消息長度之間的調整值。比較復雜,一般不使用。
  • initialBytesToStrip解碼后需要跳過的初始字節數

以下舉出幾個例子幫助理解這幾個參數(參考了 LengthFieldBasedFrameDecoder 的 JavaDoc,Magic 表示校驗消息的魔數,Length 代表消息長度,Actual Content 代表消息內容):

3.3.1 案例一

參數配置:

// 長度字段的長度為 2,長度字段代表消息內容的長度
lengthFieldOffset = 0;
lengthFieldLength = 2;
initialBytesToStrip = 0;

解碼過程:

解碼前 (14 字節)					解碼后 (14 字節)
+--------+----------------+		+--------+----------------+
| Length | Actual Content |---->| Length | Actual Content |
| 0x000C | "Hello, Netty" |		| 0x000C | "Hello, Netty" |
+--------+----------------+		+--------+----------------+

3.3.2 案例二

參數配置:

lengthFieldOffset = 0;
lengthFieldLength = 2;		// 長度字段的長度為 2
initialBytesToStrip = 2;	// 解碼后跳過長度字段

解碼過程:

解碼前 (14 字節)					解碼后 (12 字節)
+--------+----------------+		+----------------+
| Length | Actual Content |---->| Actual Content |
| 0x000C | "Hello, Netty" |		| "Hello, Netty" |
+--------+----------------+		+----------------+

3.3.3 案例三

參數配置:

// 魔數字段的長度為 2
lengthFieldOffset = 2;		// 長度字段位于魔數字段的右邊,需要偏移 2 字節
lengthFieldLength = 2;		// 長度字段的長度為 2
initialBytesToStrip = 4;	// 解碼后跳過長度和魔數字段

解碼過程:

解碼前 (16 字節)								解碼后 (12 字節)
+--------+--------+----------------+		+----------------+
| Magic  | Length | Actual Content |------->| Actual Content |
| 0x0013 | 0x0010 | "Hello, Netty" |		| "Hello, Netty" |
+--------+--------+----------------+		+----------------+

4. 總結

TCP 協議的設計目標是 高效傳遞字節流,所以沒有考慮到消息的邊界。由于 Nagle 算法、滑動窗口、MSS 限制 的因素,可能會導致 TCP 傳輸出現 粘包/拆包 的問題,這時就需要通過應用層來解決了。

應用層一般有三種解決方案:根據固定的消息長度分割消息根據固定的分隔符分割消息通過傳輸的消息長度分割消息。最常用的第三種方案,前兩種方案有一定的缺陷。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/74626.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/74626.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/74626.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JavaScript Fetch API

簡介 fetch() API 是用于發送 HTTP 請求的現代異步方法,它基于 Promise,比傳統的 XMLHttpRequest 更加簡潔、強大 示例 基本語法 fetch(url, options).then(response > response.json()).then(data > console.log(data)).catch(error > con…

UMI-OCR Docker 部署

額外補充 Docker 0.前置條件 部署前,請檢查主機的CPU是否具有AVX指令集 lscpu | grep avx 輸出如下即可繼續部署 Flags: ... avx ... avx2 ... 1.下載dockerfile wget https://raw.githubusercontent.com/hiroi-sora/Umi-OCR_runtime_linux/main/Do…

C++ --- 二叉搜索樹

1 二叉搜索樹的概念 ?叉搜索樹?稱?叉排序樹,它或者是?棵空樹,或者是具有以下性質的?叉樹: 1 若它的左?樹不為空,則左?樹上所有結點的值都?于等于根結點的值 2 若它的右?樹不為空,則右?樹上所有結點的值都?于等于根結點…

跨語言語言模型預訓練

摘要 最近的研究表明,生成式預訓練在英語自然語言理解任務中表現出較高的效率。在本研究中,我們將這一方法擴展到多種語言,并展示跨語言預訓練的有效性。我們提出了兩種學習跨語言語言模型(XLM)的方法:一種…

文件描述符,它在哪里存的,exec()后還存在嗎

學過計系肯定了解 寄存器、程序計數器、堆棧這些 程序運行需要的資源。 這些是進程地址空間。 而操作系統分配一個進程資源時,分配的是 PCB 進程控制塊。 所以進程控制塊還維護其他資源——程序與外部交互的資源——文件、管道、套接字。 文章目錄 文件描述符進程管…

Slidev使用(一)安裝

文章目錄 1. **安裝位置**2. **使用方式**3. **適用場景**4. **管理和維護** 全局安裝1. **檢查 Node.js 和 npm 是否已安裝**2. **全局安裝 Slidev CLI**3. **驗證安裝是否成功**4. **創建幻燈片文件**5. **啟動 Slidev**6. **實時編輯和預覽**7. **構建和導出(可選…

第二十一章:模板與繼承_《C++ Templates》notes

模板與繼承 重點和難點編譯與測試說明第一部分:多選題 (10題)第二部分:設計題 (5題)答案與詳解多選題答案:設計題參考答案 測試說明 重點和難點 21.1 空基類優化(EBCO) 知識點 空基類優化(Empty Base Cla…

AOA與TOA混合定位,MATLAB例程,自適應基站數量,三維空間下的運動軌跡,濾波使用EKF

本代碼實現了一個基于 到達角(AOA) 和 到達時間(TOA) 的混合定位算法,結合 擴展卡爾曼濾波(EKF) 對三維運動目標的軌跡進行濾波優化。代碼通過模擬動態目標與基站網絡,展示了從信號測量、定位解算到軌跡濾波的全流程,適用于城市峽谷、室內等復雜環境下的定位研究。 文…

量子計算:開啟未來計算的新紀元

一、引言 在當今數字化時代,計算技術的飛速發展深刻地改變了我們的生活和工作方式。從傳統的電子計算機到如今的高性能超級計算機,人類在計算能力上取得了巨大的進步。然而,隨著科技的不斷推進,我們面臨著越來越多的復雜問題&…

AMD機密計算虛擬機介紹

一、什么機密計算虛擬機 機密計算虛擬機 是一種基于硬件安全技術(如 AMD Secure Encrypted Virtualization, SEV)的虛擬化環境,旨在保護虛擬機(VM)的 ?運行中數據?(包括內存、CPU 寄存器等)免受外部攻擊或未經授權的訪問,即使云服務提供商或管理員也無法窺探。 AMD …

如何通過數據可視化提升管理效率

通過數據可視化提升管理效率的核心方法包括清晰展示關鍵指標、及時發現和解決問題、支持決策優化。其中,清晰展示關鍵指標尤為重要。通過數據可視化工具直觀地呈現關鍵績效指標(KPI),管理者能快速、準確地理解業務現狀&#xff0c…

.git 文件夾

文件夾介紹 🍎 在 macOS 上如何查看 .git 文件夾? ? 方法一:終端查看(最推薦) cd /你的項目路徑/ ls -a-a 參數表示“顯示所有文件(包括隱藏的)”,你就能看到: .git…

MongoDB 與 Elasticsearch 使用場景區別及示例

一、核心定位差異 ?MongoDB? ?定位?:通用型文檔數據庫,側重數據的存儲、事務管理及結構化查詢,支持 ACID 事務?。?典型場景?: 動態數據結構存儲(如用戶信息、商品詳情)?。需事務支持的場景&#xf…

【深度學習基礎 2】 PyTorch 框架

目錄 一、 PyTorch 簡介 二、安裝 PyTorch 三、PyTorch 常用函數和操作 3.1 創建張量(Tensor) 3.2 基本數學運算 3.3 自動求導(Autograd) 3.4 定義神經網絡模型 3.5 訓練與評估模型 3.6 使用模型進行預測 四、注意事項 …

uniapp中APP上傳文件

uniapp提供了uni.chooseImage(選擇圖片), uni.chooseVideo(選擇視頻)這兩個api,但是對于打包成APP的話就沒有上傳文件的api了。因此我采用了plus.android中的方式來打開手機的文件管理從而上傳文件。 下面…

推陳換新系列————java8新特性(編程語言的文藝復興)

文章目錄 前言一、新特性秘籍二、Lambda表達式2.1 語法2.2 函數式接口2.3 內置函數式接口2.4 方法引用和構造器引用 三、Stream API3.1 基本概念3.2 實戰3.3 優勢 四、新的日期時間API4.1 核心概念與設計原則4.2 核心類詳解4.2.1 LocalDate(本地日期)4.2…

樹莓派5從零開發至脫機腳本運行教程——1.系統部署篇

樹莓派5應用實例——工創視覺 前言 哈嘍,各位小伙伴,大家好。最近接觸了樹莓派,然后簡單的應用了一下,學習程度并不是很深,不過足夠剛入手樹莓派5的小伙伴們了解了解。后面的幾篇更新的文章都是關于開發樹莓派5的內容…

GPT Researcher 的win docker安裝攻略

github網址是:https://github.com/assafelovic/gpt-researcher 因為docker安裝方法不夠清晰,因此寫一個使用方法 以下是針對 Windows 系統 使用 Docker 運行 AI-Researcher 項目的 詳細分步指南: 步驟 1:安裝 Docker 下載 Docke…

【后端】【Django DRF】從零實現RBAC 權限管理系統

Django DRF 實現 RBAC 權限管理系統 在 Web 應用中,權限管理 是一個核心功能,尤其是在多用戶系統中,需要精細化控制不同用戶的訪問權限。本文介紹如何使用 Django DRF 設計并實現 RBAC(基于角色的訪問控制)系統&…

C#基礎學習(五)函數中的ref和out

1. 引言:為什么需要ref和out? ?問題背景:函數參數默認按值傳遞,值類型在函數內修改不影響外部變量;引用類型重新賦值時外部對象不變。?核心作用:允許函數內部修改外部變量的值,實現“雙向傳參…