文章目錄
- 前言
- UDP 協議
- UDP 的關鍵指標/特性
- UDP 的典型應用場景
- KCP 協議的基礎
- KCP 的構造
- KCP 協議特性
- KCP 的可靠傳輸機制——ARQ
- 三種 ARQ 機制對比
- KCP 的選擇性重傳
- 一、基礎機制:選擇性重傳(SR)
- 二、KCP 對 SR 的增強策略
- KCP 的激進重傳策略——低延遲設計!
- **1、 快速重傳 (Fast Retransmit):跳過漫長的等待**
- **2、選擇性重傳 (Selective Repeat / SACK-like Behavior):精準打擊,避免浪費**
- 低延遲 ACK 機制能綜合實現 “選擇性重傳” + “快速重傳”
- **3、激進的超時重傳 (Aggressive RTO)——解決尾包延遲問題**
- 低延遲的本質就是用帶寬換時間
- 再低延遲也總有意外——KCP 超時淘汰機制
- KCP 滑動窗口
- KCP 的超時淘汰機制
- KCP 應對網絡擁塞的方案——非退讓流量控制
- 總結
推薦一個零聲教育學習教程,個人覺得老師講得不錯,分享給大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK等技術內容,點擊立即學習: https://github.com/0voice 鏈接。
前言
我是零聲學院的小學徒,我雖然不是計算機科班出身,但我是學數學的,我認為對于一個我不熟悉的東西是需要作知識匯總的,盡量讓自己理解清楚事情的全貌,并且對知識作出恰當的比喻和類比,以達本意。我將仿效上一篇介紹 TCP 協議棧的文章筆法(原文鏈接 在此),詳細的介紹在工程上(尤其是游戲工程)具有廣泛應用的可靠、低延遲的通信協議——KCP.
先運行過去寫過的代碼(與 UDP 協議相關),執行過程使用 Wireshark 去抓包,記錄一路上的 UDP 報文。從現實的 UDP 報文出發,先介紹 UDP 協議棧的簡單簡陋,同時又看到它的低延遲潛力,只要我們在此基礎上,稍做設計,便能設計出可靠的低延遲傳輸協議——KCP。 而后逐步地深入介紹 KCP 協議棧的各個機制的內容,給大家一個對 KCP 的整體性的認知。事不宜遲,我們現在開始。
我過去曾寫過一篇文章,它是介紹遠程 DNS 域名查詢服務的,原文鏈接 在此。在那篇文章中,傳輸層是采用 UDP
協議的,讀者可查看那篇文章的代碼,套接字使用數據報模式就是使用 UDP 的開始,
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 這是一個通訊方式的參數設定// AF_INET 表示使用 IPv4 的地址協議。// SOCK_DGRAM 表示“數據報套接字”// 0 指代自動選擇與 type 匹配的默認協議。對于 SOCK_DGRAM,默認協議是 UDP(等價于顯式指定 IPPROTO_UDP)。// 返回的整數是一個文件描述符,唯一標識當前創建的套接字。// 后續通過此描述符進行網絡操作:bind():綁定 IP 和端口(服務端)。sendto():發送數據。recvfrom():接收數據。close():關閉套接字。if (sockfd < 0) { return -1; // 這表示通訊狀態設置不成功}
如果讀者要問假如我想使用 TCP 協議怎么辦?那也很簡單,套接字使用數據流模式就 OK 了。
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
回到正題上,我們把使用 UDP 傳輸協議的 DNS 的域名查詢代碼運行一下,利用這個程序去查詢零聲教育的域名
qiming@qiming:~/share/CTASK/TCP_test/DNS_test$ ./dns www.0voice.com
connect : 0
www.0voice.com has address 43.139.121.27Time to live: 0 minutes , 0 seconds
qiming@qiming:~/share/CTASK/TCP_test/DNS_test$
零聲教育的域名是 43.139.121.27
。此時,我們早已開啟 Wireshark 來進行網絡抓包了
于是找到了相匹配的兩份 UDP 報文
我們先從 UDP 協議講起,隨后就會將 KCP 協議。
UDP 協議
我們親眼看到真實的 UDP 報文后,我們再從理論的 UDP 報文來看
0 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 <-- 對齊 2 個字節 (16 位)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| checksum | <-- UDP 報文的頭部(共 8 個字節)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data Payload (可變長度) | <-- 數據荷載,實際數據,對齊值為 2 個字節
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
字段名稱 | 長度(字節) | 說明 |
---|---|---|
源端口號 | 2 | 發送方端口號,用于標識發送方的應用程序。 |
目的端口號 | 2 | 接收方端口號,用于標識接收方的應用程序。 |
長度 | 2 | UDP報文的總長度,包括UDP頭部和數據部分,以字節為單位。最小值為8字節(僅頭部)。 |
檢驗和 | 2 | 用于檢測UDP報文在傳輸過程中是否出現錯誤。 |
數據 | 可變 | 應用層提供的數據,長度可變。 |
UDP 報文是極其簡單的,沒有復雜的東西。
UDP 的關鍵指標/特性
1、無連接:
- 核心特性。 發送數據報之前不需要像 TCP 那樣進行“三次握手”建立連接。
- 意義: 通信開銷極小,延遲非常低(發送第一個數據包前幾乎沒有準備時間)。適用于需要快速響應的場景。
2、不可靠交付:
- 核心特性。 UDP 協議本身不提供任何機制來保證數據報一定能到達目的地。
- 具體表現:
- 不確認 (No Acknowledgement): 發送方發送后不會等待接收方的確認 (ACK)。
- 不重傳 (No Retransmission): 如果數據報丟失(網絡擁塞、路由器丟棄、校驗失敗等),發送方不會自動重傳。
- 不保證順序 (No Ordering Guarantee): 后發送的數據報可能比先發送的早到達接收方。UDP 不負責重新排序。
- 不防止重復 (No Duplication Prevention): 網絡問題可能導致同一個數據報被多次送達接收方(通常由底層 IP 網絡引起)。
- 意義: 可靠性保證(如果需要)必須由應用程序自己實現(例如,在應用層添加序列號、超時重傳、確認機制),或者應用程序能夠容忍這些情況(如直播丟幾幀畫面)。
3、無擁塞控制:
- UDP 沒有內置的機制來感知網絡擁塞并調整發送速率。
- 意義: UDP 發送方會盡可能快地按照應用程序的要求發送數據,無論網絡當前是否擁塞。這可能導致:
- 加劇擁塞: 在網絡已經擁塞時,UDP 流量會繼續沖擊網絡,可能使情況惡化,影響自身和其他協議(尤其是 TCP)的性能。
- 公平性問題: 對 TCP 流不公平,因為 TCP 會主動降低速率,而 UDP 不會。
- 應用程序如果需要在擁塞環境下良好運行,必須自己實現某種形式的速率控制和擁塞感知。
4、無流量控制:
- UDP 沒有機制讓接收方告訴發送方“慢一點,我處理不過來了”。
- 意義: 如果發送方發送速度遠快于接收方的處理能力(或接收方應用程序讀取數據的速度),接收方的緩沖區會溢出,導致數據報被丟棄。應用程序需要自行處理速率匹配問題。
5、面向數據報 (Message-Oriented):
- UDP 每次發送和接收的是一個完整的數據報(報文)。
- 意義:
- 保留消息邊界: 應用程序發送多少次 sendto 調用,接收方就需要對應多少次 recvfrom 調用。每個 recvfrom 調用返回一個完整的數據報(或報錯)。數據報之間是獨立的。
- 與 TCP 的字節流模型形成鮮明對比(TCP 沒有消息邊界,數據是連續的字節流)。
6、頭部開銷小:
- 固定頭部僅 8 字節,遠小于 TCP 頭部的 20 字節(通常)或更多(帶選項)。
- 意義: 對于傳輸小數據包的應用(如 DNS 查詢、心跳包),UDP 的協議開銷比例更低,效率更高。
7、支持廣播和多播(對局域網內的所有主機可見):
- UDP 數據報可以發送到:
- 單播 (Unicast): 單個目標主機(最常見)。
- 廣播 (Broadcast): 同一子網內的所有主機(目的 IP 地址為特定子網的廣播地址,如 192.168.1.255)。
- 多播 (Multicast): 訂閱了特定多播組的一組主機(目的 IP 地址為 D 類地址 224.0.0.0 - 239.255.255.255)。
- 意義: 這是 TCP 無法做到的(TCP 嚴格是點對點連接)。UDP 是實現如網絡發現、視頻會議、實時數據分發(股票行情)等多播/廣播應用的基石。路由器需要支持 IGMP 等協議來管理多播組成員關系。
UDP vs TCP 關鍵對比
特性 | UDP | TCP |
---|---|---|
連接 | 無連接 | 面向連接 (三次握手) |
可靠性 | 不可靠 (不確認、不重傳、不排序、不保序) | 可靠 (確認、重傳、排序、保序) |
交付保證 | 盡力而為交付 (Best Effort) | 保證按序、無差錯、不丟失、不重復的交付 |
流量控制 | 無 | 有 (滑動窗口) |
擁塞控制 | 無 | 有 (多種復雜算法) |
數據模型 | 面向數據報 (保留消息邊界) | 面向字節流 (無消息邊界) |
頭部大小 | 小 (8 字節) | 大 (通常 20 字節 + 選項) |
速度/延遲 | 快 (連接建立開銷小,無確認重傳延遲) | 相對較慢 (連接建立/斷開開銷,確認重傳延遲) |
廣播/多播 | 支持 | 不支持 (僅單播) |
適用場景 | 實時應用、簡單查詢、多播廣播、容忍丟包 | Web 瀏覽、文件傳輸、郵件、需要可靠性的應用 |
UDP 的典型應用場景
1、對延遲敏感,能容忍少量丟失的應用:
- 實時音視頻流 (VoIP, Video Conferencing, Live Streaming)
- 在線游戲 (狀態更新、位置同步)
- 實時金融行情推送
2、簡單查詢/響應,數據量小:
- DNS (域名解析)
- DHCP (動態獲取 IP 地址)
- SNMP (網絡管理)
- TFTP (簡單文件傳輸)
3、多播和廣播應用:
- 服務發現 (如 mDNS/Bonjour)
- 路由協議更新 (如 RIP)
- 流媒體分發
4、在應用層實現可靠性的場景:
- 某些自定義協議或基于 UDP 構建的可靠傳輸協議 (如 QUIC - HTTP/3 的基礎, DCCP, 部分 VPN 協議)。
UDP 是一種極其精簡、快速但“不可靠”的傳輸層協議。它的核心價值在于低延遲 和 低開銷,適用于那些對時效性要求極高、能夠容忍一定程度數據丟失或由應用層自行處理可靠性的場景。理解其報文結構(特別是端口號、長度、校驗和的作用)以及無連接、不可靠、無流控/擁控的特性,是正確使用 UDP 或選擇 UDP 還是 TCP 的關鍵。雖然 UDP 本身簡單,但在其之上構建可靠的應用層協議(如 QUIC)是當前網絡協議發展的一個重要方向。
KCP 協議的基礎
我們知道 UDP 的傳輸機制與 TCP 是極其不一樣的,強調 低延遲 和 低開銷,在游戲等領域有著廣泛的應用。那么,如何在 UDP 基礎之上,作些改動,讓其變得更加可靠但依舊低延遲低開銷呢?KCP 協議通信方案!
KCP的全稱是Quick UDP Protocol,即“快速用戶數據報協議”。KCP 協議是一種基于 UDP 的可靠傳輸協議,由國內開發者 skywind3000 設計并開源。它的核心目標是在保證一定可靠性的前提下,盡可能地降低傳輸延遲,特別適合對實時性要求高的網絡應用。
KCP 通過 24 字節精簡頭部 + 雙隊列緩沖管理(發送/接收隊列 + 緩沖區) + 時間戳驅動更新(滑動窗口的超時淘汰機制),在 UDP 上實現了低延遲可靠傳輸。其核心代碼約 1000 行 C 語言(見 GitHub),適合嵌入實時應用(如游戲引擎)。開發者需注意它不是 TCP 的替代品,而是為特定場景優化的傳輸層增強工具。
KCP 的構造
KCP 協議通過自定義報文頭在 UDP 之上構建可靠傳輸,其設計追求簡潔高效和低延遲。以下是 KCP 報文結構、核心字段和代碼實現的關鍵解析:
struct kcp_segment_header {uint32_t conv; // 會話 ID(虛擬連接標識)uint8_t cmd; // 命令類型(見下表)uint8_t frg; // 分片編號(0=最后一個分片,>0 表示后續還有分片)uint16_t wnd; // 接收窗口剩余大小(接收方能接收的數據量)uint32_t ts; // 時間戳(發送時刻,用于 RTT 計算)uint32_t sn; // 序列號(當前包的序號)uint32_t una; // 待確認序號(發送方尚未收到的 ACK 的起始序號)uint32_t len; // 數據長度(不含頭部的數據部分長度)
};
? 頭部固定 24 字節,后續緊跟用戶數據(若有)。
網絡套接字socket 與 KCP 控制塊相關聯(這與 TCP 一樣,TCP 協議棧維護 TCB,即 TCP 控制塊),它主要是放置 KCP 協議棧的內容(里面當然是包括 “滑動窗口” 的)。
// KCP 會話控制塊(簡化版)
struct ikcpcb {// 連接標識uint32_t conv;// 窗口管理uint32_t snd_una; // 最早未確認包序號uint32_t snd_nxt; // 下一個待發包序號uint32_t rcv_nxt; // 下一個待接收包序號// 隊列管理struct IQUEUEHEAD snd_queue; // 待發送隊列struct IQUEUEHEAD rcv_queue; // 亂序接收隊列struct IQUEUEHEAD snd_buf; // 已發送等待 ACK 隊列struct IQUEUEHEAD rcv_buf; // 按序接收隊列// 超時管理uint32_t current; // 當前時間戳uint32_t rx_rttval; // RTT 波動值uint32_t rx_srtt; // 平滑 RTTuint32_t rx_rto; // 當前 RTO 值// 配置參數uint32_t mtu; // 最大傳輸單元(默認 1400)uint32_t fastresend; // 快速重傳觸發閾值(默認 2)uint32_t nodelay; // 是否啟用無延遲模式(0/1)
};
KCP 協議特性
以下是 KCP 協議的關鍵特點和原理:
- 底層使用 UDP:
- KCP 本身不處理底層網絡傳輸,它構建在 UDP 之上。這意味著它繼承了 UDP 的無連接、低開銷、無擁塞控制(默認)等特性。
- 它通過自己的機制在 UDP 之上實現了可靠傳輸。
- 核心機制:ARQ (自動重傳請求):
- 和 TCP 一樣,KCP 也使用確認號、超時重傳、序列號等機制來保證數據包的可靠、有序到達。
- 它實現了自己的滑動窗口來進行流量控制。
- 追求低延遲的關鍵設計:
- 更激進的重傳策略: 這是 KCP 降低延遲的核心。
- 快速重傳: 當檢測到丟包(比如收到 3 個重復的 ACK)時,KCP 會立即重傳丟失的包,而不像標準的 TCP RTO (重傳超時) 那樣需要等待一個較長的超時時間(通常至少 200ms)。KCP 的 RTO 最小值可以設置得非常低(如 10ms 甚至更低)。
- 選擇性重傳: KCP 可以選擇性地只重傳真正丟失的數據包,而不是像 TCP SACK 出現之前那樣可能需要重傳整個窗口的數據(盡管現代 TCP 也支持 SACK)。
- 非退讓的流控: KCP 的流量控制算法(窗口大小調整)不像 TCP 那樣對丟包反應“劇烈”。TCP 在發生擁塞(丟包)時會大幅減小擁塞窗口(cwnd),導致傳輸速率驟降。KCP 的窗口調整可以配置得更平滑,減少因單次丟包造成的傳輸速率波動和恢復時間,從而保持更穩定的低延遲。用戶可以根據網絡狀況靈活配置其擁塞控制策略的激進程度。
- 更小的傳輸單元: KCP 鼓勵更小的數據分片傳輸,有助于降低單個包丟失對整個傳輸的影響。
- 更快的 ACK: KCP 可以配置為更及時地發送 ACK 確認包,減少發送端等待確認的時間。
- 更激進的重傳策略: 這是 KCP 降低延遲的核心。
性能方面的優點:
- 顯著降低延遲: 在存在一定丟包和波動的網絡環境下(如移動網絡、跨國網絡),KCP 通常能比 TCP 提供低得多的端到端延遲。
- 可配置性強: 開發者可以通過調整一系列參數(如 RTO 最小值、快速重傳觸發條件、窗口大小、是否啟用 nodelay 等)來精細控制 KCP 的行為,在延遲、帶寬利用率和抗丟包能力之間進行權衡,以適應不同的應用場景和網絡條件。
- 用戶態實現: 作為應用層協議,易于集成和調試,不依賴操作系統內核。
性能方面的缺點/代價:
- 更高的帶寬消耗: 更激進的重傳和更快的 ACK 意味著在網絡狀況不佳時,KCP 會比 TCP 產生更多的冗余數據包(重傳包、ACK包),占用更多帶寬。
- 可能加劇網絡擁塞: 如果配置過于激進且網絡本身已經擁塞,KCP 的大量重傳可能會進一步惡化網絡狀況(需要合理配置流控和擁塞避免策略)。TCP 的“退讓”特性在維持整個網絡穩定性方面是有價值的。
- 實現復雜度: 應用開發者需要理解并合理配置 KCP 的參數才能達到最佳效果,這比直接使用 TCP 稍復雜。
- 可靠性依賴實現: 其可靠性完全依賴于自身的 ARQ 實現,不像 TCP 經過了幾十年的廣泛驗證和優化。
主要應用場景:
- 實時網絡游戲: 尤其是動作類、MOBA、FPS 等對延遲極其敏感的游戲,是 KCP 最經典的應用領域。手游中使用尤其廣泛。
- 實時音視頻通話: 如視頻會議、直播連麥等,需要低延遲保證流暢互動。
- 弱網環境優化: 在移動網絡(4G/5G)、長距離鏈路(跨國)、或質量不穩定的 Wi-Fi 環境下,提升應用的響應速度和流暢性。
- 需要低延遲的遠程控制/操作: 如云桌面、遠程機器人控制等。
- 替代 TCP 在某些特定延遲敏感場景的應用。
那么 KCP 與 TCP 有何相同點呢?
KCP 與 TCP 的基礎可靠性機制相同
機制 | 作用 | KCP & TCP 實現方式 |
---|---|---|
序列號 (Sequence Number) | 標識數據包順序 | 每個數據包攜帶唯一序列號,接收方按序重組 |
確認應答 (ACK) | 告知發送方數據接收狀態 | 接收方發送 ACK 包(含已接收的最大連續序列號) |
超時重傳 (RTO) | 應對丟包 | 未收到 ACK 的包在超時后重傳 |
滑動窗口 (Sliding Window) | 流量控制與批量傳輸 | 限制發送方可發送的數據量,通過 ACK 推進窗口釋放空間 |
? 兩者均通過 序列號 + ACK + 重傳 + 滑動窗口 實現可靠傳輸(本質是 ARQ 自動重傳請求)
KCP 與 TCP 的滑動窗口的核心邏輯相同
概念 | 作用 | KCP & TCP 一致性 |
---|---|---|
發送窗口 (snd_una) | 跟蹤最早未確認包 | 窗口左邊界,收到 ACK 后向右移動 |
接收窗口 (rcv_nxt) | 期待接收的下一個包序號 | 接收方按序交付數據后向右移動 |
窗口大小 (snd_wnd/rcv_wnd) | 控制傳輸速率 | 限制發送速率(避免淹沒接收方) |
? 窗口滑動邏輯完全一致:ACK 推動發送窗口,連續數據推動接收窗口。
KCP 與 TCP 的基礎組件相同
組件 | 功能 | KCP & TCP |
---|---|---|
重傳計時器 | 檢測丟包超時 | 每個發送包獨立計時,超時觸發重傳 |
緩沖區管理 | 緩存已發送未確認/亂序到達的數據 | 發送/接收端均維護隊列 |
校驗和 | 檢測數據損壞 | 可選支持(KCP 依賴下層 UDP 校驗,TCP 強制) |
總結:異同本質
維度 | KCP | TCP |
---|---|---|
基礎結構 | ? 與 TCP 完全相同 | ? 標準滑動窗口 |
窗口單位 | ? 包數量(非字節) | 字節數 |
控制權 | ? 應用層可編程 | 內核固定控制 |
擁塞控制 | ? 需開發者實現 | 內置完善算法 |
ACK 機制 | ? 支持無延遲 ACK + 精確重傳信息 | 依賴系統實現和擴展 |
KCP 的可靠傳輸機制——ARQ
KCP 協議的核心 ARQ(自動重傳請求)策略是 選擇性重傳(Selective Repeat, SR),但在此基礎上進行了深度優化和激進參數調整。其實,這點跟 TCP 沒啥區別。
三種 ARQ 機制對比
ARQ(Automatic Repeat reQuest) 是可靠傳輸協議的核心機制,其三種經典實現方式——停等式(Stop-and-Wait)、回退 N 幀(Go-Back-N) 和選擇性重傳(Selective Repeat)——構成了現代網絡協議(如 TCP、KCP)的基礎。以下是它們的詳細對比及與 KCP/TCP 的關聯:
特性 | 停等式 (Stop-and-Wait) | 回退 N 幀 (Go-Back-N, GBN) | 選擇性重傳 (Selective Repeat, SR) |
---|---|---|---|
發送窗口大小 | 1 個包 | N 個包(N>1) | N 個包(N>1) |
接收窗口大小 | 1 個包 | 1 個包 | N 個包 |
重傳策略 | 超時后重發當前包 | 超時后重發所有未確認包 | 僅重傳丟失的包 |
亂序處理 | 丟棄亂序包 | 丟棄亂序包 | 緩存亂序包 |
傳輸效率 | ? 最低(每包需單獨確認) | ?? 中等(批量發送但重傳開銷大) | ??? 最高(僅重傳丟失包) |
帶寬利用率 | 低(RTT 內只能發 1 個包) | 中(流水線發送,但重傳冗余) | 高(精準重傳 + 亂序緩存) |
典型場景 | 極弱網絡(如衛星鏈路) | 早期 TCP(如 Tahoe) | 現代 TCP(SACK)/ KCP |
停等式 (Stop-and-Wait) 有點小撈,它太笨了。? 問題:每個包必須等 ACK 到達才能發下一個,效率極低。
回退 N 幀 (Go-Back-N, GBN) 處理的批量很大。? 問題:單包丟失導致大量冗余重傳(尤其高延遲網絡)。
選擇性重傳 (Selective Repeat, SR) 就具有相當的現代性了。? 優勢:僅重傳丟失包(SN=2),接收方緩存亂序包(3,4,5)。
KCP 的選擇性重傳
一、基礎機制:選擇性重傳(SR)
特性 | KCP 的實現 |
---|---|
重傳對象 | 僅重傳真正丟失的包(非整個窗口) |
亂序處理 | 接收方緩存亂序到達的包,待缺失包到達后按序提交 |
確認機制 | ACK 包攜帶精確的丟包信息(類似 TCP SACK,但為原生支持) |
效率優勢 | 單包丟失不影響后續包傳輸,避免回退 N 幀(GBN)的冗余重傳 |
? 核心邏輯:“誰丟傳誰” —— 通過 ACK 包中的序列號信息精準定位丟失包。
二、KCP 對 SR 的增強策略
1.、激進快速重傳
- 觸發條件:收到 2 個重復 ACK(默認值,可配置)即重傳(TCP 需 3 個)。
- 優勢:將丟包檢測時間從 RTT + 超時等待 壓縮至 ≈1 RTT。
2、超時重傳優化
- RTO 最小值:可設為 5-10ms(TCP 通常 ≥200ms),加速兜底重傳。
- RTO 計算:啟用 nodelay 模式時,RTO = RTT + 1×RTTVAR(更貼近當前延遲)。
3、ACK 無延遲反饋
- 機制:啟用 ack_nodelay 時,收到數據包立即回復 ACK(TCP 常延遲 40ms 等待捎帶)。
- 效果:進一步減少丟包判定時間(尤其對單向數據流)。
4、 窗口淘汰機制
- 問題場景:最早發送的包(如 SN=10)多次重傳失敗,阻塞窗口滑動。
- KCP 方案:
- 若后續包(如 SN=15)被確認 → 強制將窗口左邊界推進到 SN=16。
- 放棄舊包的重傳,避免傳輸停滯(犧牲少數包可靠性換流動性)。
KCP 的激進重傳策略——低延遲設計!
KCP 的低延遲主要來源于它對 丟包 這一網絡傳輸最大敵人的處理方式。TCP 為了保證網絡的全局公平性和穩定性,對丟包的反應是相對“保守”和“緩慢”的。而 KCP 的設計哲學是:為了應用的實時性,可以更早、更主動、更精準地重傳丟失的數據包,即使這意味著可能消耗更多的帶寬。
1、 快速重傳 (Fast Retransmit):跳過漫長的等待
- 原理: 當接收方發現接收到的數據包序列號不是連續的(即出現“空洞”),它會立即向發送方發送一個 ACK 包,指出它期望接收的下一個序列號(即空洞的開始位置)。如果發送方連續收到 3個(默認值,可配置) 或更多針對同一個數據包的重復 ACK (DupACK),它就立即推斷該數據包很可能丟失了。
- 與 TCP 的關鍵區別:
- 觸發閾值更低: TCP 通常也需要 3 個 DupACK 觸發快速重傳(NewReno 及以后算法)。看起來一樣?不!關鍵在于時間窗口。
- RTO 最小值極低: TCP 的 RTO (Retransmission Timeout) 計算復雜,其最小值通常被限制在 200ms 左右(Linux 默認)。這意味著,即使觸發了快速重傳,TCP 也可能在等待 RTO 超時后才行動,或者其 RTO 本身就很大。KCP 的 RTO 最小值 (minrto) 可以由用戶直接設置,典型值低至 10ms 甚至 5ms。即使沒有 DupACK,KCP 的常規超時重傳等待時間也遠短于 TCP。
- 反應速度: 當收到足夠的 DupACK 時,KCP 不等待任何定時器超時,立即重傳懷疑丟失的數據包。而 TCP 在收到 DupACK 后,雖然會啟動快速重傳,但可能受到擁塞窗口調整、RTO 計算等因素影響,響應速度不如 KCP 直接和激進。
- 延遲收益: 在發生單包或少量丟包時,KCP 能在 幾十毫秒內 完成重傳(收到 DupACK -> 立即重發),而 TCP 可能因為 RTO 較大或算法保守需要等待 幾百毫秒。這對于實時應用(如游戲中一個關鍵操作指令丟失)是質的飛躍。
2、選擇性重傳 (Selective Repeat / SACK-like Behavior):精準打擊,避免浪費
- 原理: 當接收方檢測到多個不連續的數據包丟失時,它需要告知發送方具體是哪些包丟了。KCP 在其 ACK 包中攜帶了接收窗口內所有缺失數據段的起始和結束序列號信息(類似于 TCP 的 SACK 選項,但實現方式不同)。
- 與 TCP 的關鍵區別:
- TCP 早期版本 (Reno): 在收到 DupACK 進入快速恢復階段時,如果只丟失一個包,效果尚可。但如果一個窗口內丟失多個包,Reno 在收到部分新數據的 ACK 后就會錯誤地認為所有丟失包都已重傳成功,退出快速恢復,導致剩余丟失包必須等待 RTO 超時(即所謂的“重傳超時”問題),造成巨大的延遲(幾百毫秒到幾秒)。
- TCP 現代版本 (SACK): 通過 SACK 選項也能實現選擇性確認,解決了 Reno 的多個丟包問題。KCP 與 TCP SACK 在“選擇性”理念上相似,但 KCP 的實現通常更輕量和直接,并且其整體重傳策略(結合極低的 RTO 和快速響應)更為激進。
- 延遲收益: 在發生多個數據包連續丟失的情況下,KCP 能通過接收方精確的反饋,一次性重傳所有已知丟失的包,避免了像 TCP Reno 那樣因多次 RTO 超時或等待導致的累積延遲。即使對比 TCP SACK,KCP 更快的觸發和重傳動作也能進一步壓縮恢復時間。
低延遲 ACK 機制能綜合實現 “選擇性重傳” + “快速重傳”
想象一個場景:在我們中學時代,監考老師給我們點名,他們會一個一個地點,點一個學生就喊一句 “到”,當點到下一位,無任何人應答的時候,老師立馬就把這個學生記到遲到缺席的 “黑名單” 里,并通知班主任叫他趕快來,在此期間一直等他,直到老師不耐煩了,把它記作缺考(超時淘汰)。這其實就是一個低延遲 ACK 。
以下這個圖示正是一個低延遲 ACK 的工作原理展示。
時間線 (向下流動) 發送方 (Sender) 接收方 (Receiver) 事件說明
------------------- --------------------------- --------------------------- --------------------------------| 發送 Seq1 -> || 發送 Seq2 -> || 發送 Seq3 (丟失) -> (丟失) **Seq3 在網絡中丟失**| 發送 Seq4 -> || 發送 Seq5 -> || <- ACK1 (確認Seq1) **低延遲 ACK!**| <- ACK2 (確認Seq2) **低延遲 ACK!**| <- ACK2 (重復ACK) **收到 Seq4 (亂序),立即重復 ACK2 索要 Seq3**| <- ACK2 (重復ACK) **收到 Seq5 (亂序),立即重復 ACK2 索要 Seq3**| **發送方收到第2個重復ACK(2) (假設fastresend=2)**| 重傳 Seq3 -> **快速重傳觸發! (只重傳丟失的Seq3)**| <- ACK3 (確認Seq3) **低延遲 ACK! Seq3到達**| **接收方已緩存 Seq4, Seq5,現在可交付 Seq3, Seq4, Seq5**| <- ACK5 (確認Seq5) **累積確認/選擇性ACK (確認Seq3,4,5)**| 發送 Seq6 -> | 繼續發送新數據| <- ACK6 (確認Seq6) || ... ... ...
當 KCP 收到一個數據包時,它不會等待interval定時器到期或等待有數據要回傳時捎帶 ACK,而是立即生成一個 ACK 包發送回去。
更及時的 ACK 反饋:減少發送端的等待
- 原理: KCP 提供了靈活的 ACK 發送策略:
- ack_nodelay: 啟用后(推薦),當 KCP 收到一個數據包時,它不會等待interval定時器到期或等待有數據要回傳時捎帶 ACK,而是立即生成一個 ACK 包發送回去。
- 禁用時,會嘗試稍作等待(interval內)以便捎帶數據或合并 ACK。
- 與 TCP 的關鍵區別: TCP 通常采用捎帶確認策略,即接收方在發送數據時,才把 ACK 信息放在回傳數據包的首部中一起發回。如果沒有數據要回傳,TCP 會設置一個小的延遲定時器(通常幾十毫秒)等待,希望有數據可以捎帶。如果沒有,才單獨發送一個純 ACK 包。這個延遲是為了減少純 ACK 包的數量。
- 延遲收益: 啟用 ack_nodelay 后,發送方能更快地知道數據包是否成功到達。這帶來兩個好處:
- 加速快速重傳觸發: 丟失包的 DupACK 能更快地返回到發送方。
- 推進發送窗口: 確認包能更快地釋放發送窗口的空間,允許發送方發送后續的新數據,提高吞吐量和降低整體傳輸完成時間。
- 代價:可能增加少量純 ACK 包的帶寬消耗。
3、激進的超時重傳 (Aggressive RTO)——解決尾包延遲問題
- 原理: KCP 使用自己獨立的 RTO 計算算法(基于 RTT 測量和變化)。雖然算法本身與 TCP 的 Karn/Partridge 算法類似,但其核心參數可由用戶精細控制:
- minrto: 允許設置的最小 RTO 值(如 10ms)。TCP 通常有系統級下限(200ms)。
- nodelay: 啟用后(通常推薦啟用),會顯著改變 RTO 計算方式:
- 第一次 RTO 計算:RTO = RTT + max(clock_drift, 4 * RTTVAR)
- 后續 RTO 計算:RTO = RTT + 1 * RTTVAR (非常接近最新的 RTT 測量值)
- interval: KCP 內部輪詢間隔(如 10ms)。這決定了它檢查超時的頻率。
- 與 TCP 的關鍵區別:
- 用戶可配置性: 開發者可以根據網絡環境和應用需求,將 minrto 設置得非常低(如 10ms),而 TCP 的內核參數調整通常受限且影響全局。
- 更快的檢測: 更小的 interval 和 minrto 意味著 KCP 能更快地檢測到超時(可能沒有觸發快速重傳的丟包,如尾包丟失)。
- 更激進的增長: nodelay 模式下的 RTO 計算比標準 TCP 更激進,增長幅度小,使得超時重傳的等待時間更接近最新的 RTT 測量值,而不是像 TCP 那樣指數級退避導致 RTO 迅速膨脹到秒級。
- 延遲收益: 對于那些無法觸發快速重傳(如只丟失一個包且后面沒有新數據,無法產生 DupACK)的丟包場景,KCP 能在 10ms - 幾十ms 級別 的超時后立即重傳,而 TCP 至少需要等待 200ms 以上。這解決了“尾包延遲”問題。
協同效應:如何合力實現低延遲
- 丟包檢測快: 通過 ack_nodelay 和精確的序列號/ACK機制,接收方能快速發現丟包并通知發送方 (DupACK)。
- 決策快: 收到足夠 DupACK (閾值可配,默認3) 后,KCP 立即決策重傳,不等待定時器。
- 重傳動作快: 利用選擇性重傳信息,只重傳丟失的包。
- 兜底快: 即使沒有觸發快速重傳(如尾包丟失),極低的 minrto 和快速的內部輪詢 (interval) 也能保證在極短時間(如 10-30ms)后通過超時重傳補發。
- 恢復快: 一旦丟失的包被重傳并確認,發送窗口能迅速前進(因為 ACK 回來得也快 - ack_nodelay),后續數據流能很快跟上,避免了 TCP 因 RTO 膨脹和窗口驟減導致的傳輸速率“雪崩”和緩慢恢復。
低延遲的本質就是用帶寬換時間
KCP 激進重傳策略的核心思想是:不惜以可能增加帶寬消耗(發送更多重傳包和更及時的純 ACK 包)和潛在加劇網絡擁塞為代價,換取傳輸延遲的極大降低。 它通過一系列精心設計的、高度可配置的機制,將網絡傳輸中最耗時的環節——丟包檢測、決策、重傳和恢復——的時間壓縮到了極致(毫秒級),從而滿足了實時應用對低延遲的苛刻要求。這種“帶寬換時間”的權衡在實時游戲、音視頻通信等場景下通常是值得的,因為用戶體驗對延遲的敏感度遠高于對少量額外帶寬消耗的敏感度。
再低延遲也總有意外——KCP 超時淘汰機制
我們先設想一個情景:當我們在玩英雄聯盟的時候,如果我們的電腦卡頓(ping 值很高),英雄在漂移,情況持續了 3 min,網絡恢復正常之后,系統并沒有把我們電腦卡頓過程時候,別的英雄擊殺我們的片段給出,而是快速跳過,最后只顯示我們在泉水之中。這,就是 KCP 超時淘汰機制,極其具有實用性和工程性。
KCP 滑動窗口
KCP 超時淘汰機制是從屬于 KCP 滑動窗口的管理機制的,其結構圖示如下
發送方 (Sender) 接收方 (Receiver)
+---------------------------------------------------+ +---------------------------------------------------+
| **發送緩沖區 (Send Buffer)** | | **接收緩沖區 (Recv Buffer)** |
| +-----------------------------------------------+ | | +-----------------------------------------------+ |
| | 已發送 **已確認** (Delivered & Acked) | | | | 已接收 **已交付應用** (Delivered to App) | |
| | [SND.UNA 之前的數據] | | | | [RCV.NXT 之前的數據] | |
| | | | | | | |
| +-----------------------------------------------+ | | +-----------------------------------------------+ |
| | 已發送 **未確認** (In Flight / Outstanding) | | ----> | | 已接收 **待交付** (Received, Not Delivered) | |
| | [**發送窗口 (Sending Window)** 的核心區域] | | 數據包 | | [**接收窗口 (Receiving Window)** 可用空間前部]| |
| | * SND.UNA (發送未確認起點) | | ----> | | * RCV.NXT (期望接收起點) | |
| | * SND.NXT (下一個發送位置) --> | | | | | |
| | | | | | | |
| +-----------------------------------------------+ | | +-----------------------------------------------+ |
| | **允許發送但尚未發送** (Sendable) | | | | **可用接收空間** (Available Space) | |
| | [發送窗口內,SND.NXT 之后的部分] | | | | [**接收窗口 (Receiving Window)** 核心] | |
| | * 發送窗口右邊界 = SND.UNA + Win | | | | * 窗口大小 = RCV.WND | |
| | Win = min(擁塞窗口 cwnd, 接收方通告窗口 rwnd) | | | | * 右邊界 = RCV.NXT + RCV.WND | |
| +-----------------------------------------------+ | | +-----------------------------------------------+ |
| | **不允許發送** (Not Sendable) | | | | **不可用空間** (Not Available) | |
| | [超出當前發送窗口] | | | | [超出當前接收窗口] | |
| +-----------------------------------------------+ | | +-----------------------------------------------+ |
+---------------------------------------------------+ +---------------------------------------------------+
1、發送窗口 (SND_WND) 的約束:
- KCP 發送端維護一個發送窗口,它定義了當前允許發送的最大數據范圍(從 snd_una 到 snd_una + snd_wnd)。
- snd_una 指向最早一個尚未被確認(Acknowledged)的數據包的序列號。它是窗口的左邊界。
- 只有序列號落在 [snd_una, snd_una + snd_wnd) 范圍內的數據包才允許被發送(包括首次發送和重傳)。
2、窗口的推進 (Advancing):
- 當接收方 ACK 了 snd_una 指向的包(以及按序到達的后續包)時,snd_una 就會向右移動(增大),發送窗口也隨之整體向右滑動。
- 新“暴露”出來的窗口空間允許發送新的數據包。
3、“丟包太久不重發”的本質:窗口淘汰
- 關鍵決策點: 為了不讓整個傳輸流程被這些頑固的舊丟包卡死(阻塞新數據的發送),KCP 會執行一個策略:強制將 snd_una 推進到接收方已 ACK 的最小連續序列號之后的位置。
KCP 的超時淘汰機制
我接下來通過圖示簡單介紹一下超時淘汰機制。
圖 1:初始狀態(正常傳輸)
發送窗口范圍: [10, 11, 12, 13, 14, 15] (snd_una=10, snd_wnd=6)
已發送未確認: █ █ █ █ █ █ 10 11 12 13 14 15 <-- 序列號
狀態:所有包已發送,等待 ACK。
圖 2:發生丟包(包 10,11,12 丟失)——選擇性重傳
發送窗口范圍: [10, 11, 12, 13, 14, 15]
已發送未確認: ? ? ? █ █ █ 10 11 12 13 14 15
狀態:- 包 13,14,15 被接收方收到,但無法 ACK(因包 10 未到,不連續)- 包 10,11,12 超時重傳多次(🔥)仍失敗。
圖 3:觸發淘汰機制(強制推進窗口)
接收方反饋:ACK 13,14,15(非連續確認)
KCP 決策:放棄包 10,11,12(重傳失敗)!新窗口范圍: [16, 17, 18, 19, 20, 21] (snd_una=16, snd_wnd=6)
已發送未確認: █ █ █ █ █ █ 16 17 18 19 20 21
淘汰影響:- 包 10,11,12 被移出窗口 ?(不再重傳)- 新數據包 16-21 立即發送 ?
圖 4:新丟包處理(包 17 丟失)
窗口范圍: [16, 17, 18, 19, 20, 21]
已發送未確認: █ ? █ █ █ █ 16 17 18 19 20 21
狀態:- 包 17 丟失 → 觸發 **快速重傳**(仍在窗口內)- 包 18,19,20,21 正常傳輸。
關鍵決策點: 為了不讓整個傳輸流程被這些頑固的舊丟包卡死(阻塞新數據的發送),KCP 會執行一個策略:強制將 snd_una 推進到接收方已 ACK 的最小連續序列號之后的位置。在本案例中:
1、當前 snd_una = 10。
2、包 10, 11, 12 丟失且重傳多次失敗。
3、接收方收到了包 13, 14, 15 并 ACK 了它們(但無法 ACK 10, 11, 12)。
3、KCP 發現 13, 14, 15 被 ACK 了,但 10 這個關鍵點一直沒 ACK。
4、KCP 判定 10, 11, 12 重傳無望,決定將 snd_una 直接推進到 16(假設 15 是當前收到的最大連續包)。
后果:
5、序列號 10, 11, 12 的包被移出發送窗口 ([snd_una=16, snd_una+snd_wnd)) 的范圍。
6、這些包不再有資格被重傳,因為 KCP 的重傳隊列只維護窗口內的包。
7、發送窗口騰出了大量空間,新的數據包(序列號 >=16)可以立即被發送,傳輸得以繼續。
與 TCP 的核心差異
機制 | KCP | TCP |
---|---|---|
窗口淘汰 | ? 主動放棄超時舊包,推進窗口 | ? 堅持重傳直到成功或斷開 |
阻塞避免 | ? 新數據不受舊丟包影響 | 🐢 舊丟包阻塞整個窗口 |
適用場景 | 實時應用(容忍部分丟包 | ) 高可靠性傳輸(如文件下載) |
KCP 應對網絡擁塞的方案——非退讓流量控制
讓我們用一個“高速公路應急車道管理”的比喻,來形象地理解 KCP 的非退讓流量控制的必要性,并將其與傳統 TCP 的退讓式(AIMD) 控制進行對比。
場景設定:
- 道路: 一條繁忙的高速公路,有 常規車道 和 應急車道。
- 車輛: 代表需要傳輸的數據包。
- 交通狀況: 代表網絡狀況。偶爾有小事故(輕微丟包),也可能有大擁堵(嚴重擁塞)。
- 交通管理中心 (TCP): 遵循嚴格的安全規則。一旦監測到任何事故(丟包),就認為可能發生大擁堵(嚴重擁塞),立即采取激進措施。
- 特種物流車隊 (KCP): 運輸的是高時效性、高優先級的物資(如救援物資、緊急醫療用品)。它們的核心任務是盡最大可能、以最快速度、穩定地將物資送達,即使路況不完美。它們擁有特殊的通行策略。
傳統 TCP (退讓式/AIMD,擁塞控制配合慢啟動) 的比喻:
- 策略 (“寧可錯殺,不可放過”):
- 只要高速攝像頭(丟包檢測)拍到任何一起小剮蹭事故(一個丟包),交通管理中心 (TCP) 就立刻、武斷地認定整條高速即將癱瘓(嚴重擁塞)。
- 行動:
- 立即關閉一半常規車道! (乘性減 - 擁塞窗口減半)。所有車輛(數據包)瞬間只能使用一半的道路資源。
- 然后極其緩慢地、一條一條地重新開放車道。 (加性增 - 擁塞窗口緩慢線性增長)。需要很長時間才能恢復到原來的通行能力。
- 后果:
- 交通能力瞬間腰斬: 即使事故很小且很快處理完,車流(吞吐量)也暴跌。大量無辜車輛被堵在路上。
- 恢復極其緩慢: 重新開放車道的過程非常保守和緩慢,整體運輸效率低下。
- 對高時效物資是災難: 特種物流車隊 (重要數據) 即使沒有卷入事故,也被迫降速、延誤,無法完成緊急任務。
- 必要性 (在公共道路上): 對于普通交通,這種策略是必要的,以防止自私的司機(應用)過度占用道路導致全局崩潰(擁塞崩潰)。安全第一,公平性優先。
KCP (非退讓流控) 的比喻:
-
策略 (“精準處置,保持運力”):
- 特種車隊有自己的指揮中心 (KCP協議棧)。
- 當車隊收到報告,某輛特定運輸車(一個數據包,如 Seq102)在某個路段發生了小事故(丟失)。
- 行動:
- 立即派遣一輛備用車 (快速重傳) 走應急車道 (選擇性重傳) 去替換那輛故障車。目標:最快速度補上缺失的物資。
- 絕不關閉主車道! (非退讓 - 發送窗口 snd_wnd 不縮減)。其他所有運輸車(后續數據包)繼續在主車道 (常規車道) 上按計劃行駛,只要目的地倉庫 (接收方) 還有空間 (rcv_wnd)。
- 可能微調車速 (調整 interval/RTO): 指揮中心可能會命令整個車隊稍微降低一點車速(比如從 120km/h 降到 100km/h),給處理事故留出更多余量,避免連續出事。但這只是調速,不是封路!
- 利用一切可用空間: 一旦目的地倉庫報告有更多空位(通告更大的 rcv_wnd),指揮中心立刻允許派出更多運輸車(增大 snd_wnd),填滿所有可用車道。
-
后果:
- 運力影響最小化: 只有出事的那一輛車需要替換,其他車輛幾乎不受影響。整體運輸量(吞吐量)保持高位且穩定。
- 時效性最大化: 高優先級物資總能盡快利用可用道路資源送達。平均延遲低,延遲波動小。
- 快速恢復潛力: 一旦備用車抵達(重傳成功)或路況變好,車隊可以瞬間提速或加車(利用更大的 snd_wnd),恢復到甚至超過之前的運力。
必要性 (對特種車隊):
- 任務優先: 對于救援物資、緊急醫療用品(實時音視頻、游戲指令、金融交易),送達的時效性和穩定性是最高優先級。一次大的延誤或吞吐量暴跌可能導致任務失敗(通話卡頓、游戲卡死、交易超時)。
- 容忍小事故: 這些小事故(少量丟包)在任務背景下是可以接受的代價(音頻偶爾沙沙聲、畫面小馬賽克),只要主體物資(大部分數據包)能持續、快速、穩定地送達。
- 專用/可控道路: 特種車隊通常在有優先權、路況相對可控的路線上行駛(如專線、VPN、P2P內網),或者其流量占比不高。它們不會(或不應該)主動去擠垮公共道路(公網核心)。在這種場景下,“自私”一點,優先保障自己的關鍵任務,是必要且合理的。
- 避免過度反應: 關閉一半車道(窗口減半)對于處理一個小事故是災難性的過度反應,完全違背了特種車隊的核心使命。
以下是非退讓流量控制的程序執行圖例(大家要結合低延遲 ACK 去理解),當出現丟包的時候,稍微降低傳輸速度(RTO 稍微增大,但窗口沒有像 TCP 那樣大幅縮減),當接收方告知緩沖區充足的時候,發送方增大發送窗口的大小。
時間線 (向下流動) 發送方 (Sender) 接收方 (Receiver) / 網絡事件
------------------ ---------------------------------------------- ------------------------------------------------| **狀態:**| `snd_una = 100` (下一個期望ACK是100)| `rcv_wnd (來自接收方) = 10` (最新通告)| `snd_wnd = min(rcv_wnd, max) = 10`| **發送窗口: [100, 110)**| **發送: Seq100, Seq101, Seq102, Seq103, Seq104** -> **網絡正常**| **發送窗口內剩余: 5 slots (105-109)**| **發送: Seq105, Seq106, Seq107** -> **網絡正常**| **發送窗口內剩余: 2 slots (108-109)**| **Seq102 丟失! (網絡擁塞開始)**| **接收方收到:**| Seq100 -> **立即發送 ACK(101)** (確認100)| Seq101 -> **立即發送 ACK(102)** (確認101)| Seq103 (亂序!) -> **立即發送 ACK(102)** (DUP-ACK, 索要102)| Seq104 (亂序!) -> **立即發送 ACK(102)** (DUP-ACK)| <- **收到 ACK(101)** (`snd_una` 推進到101) | **發送窗口變為: [101, 111)**| **發送: Seq108, Seq109** -> (用完當前窗口) **Seq108, Seq109 可能到達或丟失**| <- **收到 ACK(102)** (`snd_una` 推進到102) | **發送窗口變為: [102, 112)**| **無新數據,或應用層暫無數據**| <- **收到 ACK(102) (DUP-ACK 1)** **接收方處理Seq105, Seq106...**| <- **收到 ACK(102) (DUP-ACK 2) [假設fastresend=2]** **觸發快速重傳條件!**| **!! 關鍵點 !!** | **檢測到 Seq102 丟失 (通過 DUP-ACKs)**| **但 `snd_wnd` 保持不變 = 10 (非退讓!)**| **動作: 立即重傳 Seq102** -> **重傳Seq102到達**| **可能動作: 輕微增加 RTO 或 減小 `interval` (稍慢發送新數據)**| **接收方收到 Seq102:**| **立即發送 ACK(108)** (確認102-107? 或累積ACK)| **(同時,應用層消費數據,假設釋放了空間)**| **接收方 `rcv_wnd` 增大 (e.g., =15)**| <- **收到 ACK(108)** (`snd_una` 推進到108) | **發送窗口變為: [108, 118)** (因為 `snd_wnd` 仍為10)| <- **收到新通告 `rcv_wnd = 15`** | **!! 關鍵點 !!** | **更新 `snd_wnd = min(15, max) = 15`** (窗口**擴大**了!)| **發送窗口變為: [108, 123)** (立刻獲得更大發送潛力)| **發送: Seq110, Seq111, ... (直到填滿新窗口)** -> **網絡擁塞可能緩解或持續...**| **... 繼續 ...**
總結
KCP 的各項機制之間是有相當大的重合,但歸根結底,其底層運作機理是
- 低延遲 ACK :有快速重傳、選擇性重傳的效果,即可靠的 UDP 傳輸。
- 超時重傳機制:第一個計時器(在滑動窗口上的設置)是對低延遲 ACK 的補充,解決尾包延遲問題。
- 滑動窗口:有選擇性重傳的功效,有序的流量控制。
- 非退讓流量控制:這是滑動窗口上的設置。容忍小錯誤、瑕疵,以換取傳輸流暢與穩定,用戶體驗更好
- 超時淘汰機制:第二個計時器(在滑動窗口上的設置),是對嚴重超時的包的斷舍離處理。
我還會寫幾篇文章去介紹 KCP 協議的 C/C++ 代碼實現。畢竟如果只有課本理論,那么會很單調的。話又說回來,源代碼是有 1000 多行的,加上雜七雜八的應用,代碼可能直逼 2000 行。故而深入解析 KCP 協議代碼是一個長期的工程,我會繼續跟進,但是近期不會寫,我要花更多的事件去理解這些代碼,而后給大家去講述。