快速可靠網絡傳輸協議 KCP

KCP 是一個快速可靠協議,能以比 TCP浪費10%-20%的帶寬的代價,換取平均延遲降低30%-40%,且最大延遲降低三倍的傳輸效果。純算法實現,並不負責底層協議(如UDP)的收發,需要使用者自己定義下層數據包的發送方式,以 callback的方式提供給 KCP。連時鐘都需要外部傳遞進來,內部不會有任何一次系統調用。

整個協議只有 ikcp.h, ikcp.c兩個源文件,可以方便的集成到用戶自己的協議棧中。也許你實現了一個P2P,或者某個基於 UDP的協議,而缺乏一套完善的ARQ可靠協議實現,那麼簡單的拷貝這兩個文件到現有項目中,稍微編寫兩行代碼,即可使用。HTTP://www.mobanhou.COM

技術特性

TCP是爲流量設計的(每秒內可以傳輸多少KB的數據),講究的是充分利用帶寬。而KCP是爲流速設計的(單個數據包從一端發送到一端需要多少時間),以10%-20%帶寬浪費的代價換取了比 TCP快30%-40%的傳輸速度。TCP信道是一條流速很慢,但每秒流量很大的大運河,而KCP是水流湍急的小激流。KCP有正常模式和快速模式兩種,通過以下策略達到提高流速的結果:

RTO翻倍vs不翻倍:

TCP超時計算是RTOx2,這樣連續丟三次包就變成RTOx8了,十分恐怖,而KCP啓動快速   模式後不x2,只是x1.5(實驗證明1.5這個值相對比較好),提高了傳輸速度。

選擇性重傳 vs 全部重傳:

TCP丟包時會全部重傳從丟的那個包開始以後的數據,KCP是選擇性重傳,只重傳真正   丟失的數據包。

快速重傳:

發送端發送了1,2,3,4,5幾個包,然後收到遠端的ACK: 1, 3, 4, 5,當收到ACK3時,   KCP知道2被跳過1次,收到ACK4時,知道2被跳過了2次,此時可以認爲2號丟失,不用   等超時,直接重傳2號包,大大改善了丟包時的傳輸速度。

延遲ACK vs 非延遲ACK:

TCP爲了充分利用帶寬,延遲發送ACK(NODELAY都沒用),這樣超時計算會算出較大   RTT時間,延長了丟包時的判斷過程。KCP的ACK是否延遲發送可以調節。

UNA vs ACK+UNA:

ARQ模型響應有兩種,UNA(此編號前所有包已收到,如TCP)和ACK(該編號包已收到   ),光用UNA將導致全部重傳,光用ACK則丟失成本太高,以往協議都是二選其一,而   KCP協議中,除去單獨的 ACK包外,所有包都有UNA信息。

非退讓流控:

KCP正常模式同TCP一樣使用公平退讓法則,即發送窗口大小由:發送緩存大小、接收   端剩餘接收緩存大小、丟包退讓及慢啓動這四要素決定。但傳送及時性要求很高的小   數據時,可選擇通過配置跳過後兩步,僅用前兩項來控制發送頻率。以犧牲部分公平   性及帶寬利用率之代價,換取了開着BT都能流暢傳輸的效果。

基本使用

  1. 創建 KCP對象:

    // 初始化 kcp對象,conv爲一個表示會話編號的整數,和tcp的 conv一樣,通信雙
    // 方需保證 conv相同,相互的數據包才能夠被認可,user是一個給回調函數的指針
    ikcpcb *kcp = ikcp_create(conv, user);
  2. 設置回調函數:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // KCP的下層協議輸出函數,KCP需要發送數據時會調用它
    // buf/len 表示緩存和長度
    // user指針爲 kcp對象創建時傳入的值,用於區別多個 KCP對象
    int udp_output(const char *buf, int len, ikcpcb *kcp, void *user)
    {
     ....
    }
    // 設置回調函數
    kcp->output = udp_output;
  3. 循環調用 update:

    1
    2
    3
    // 以一定頻率調用 ikcp_update來更新 kcp狀態,並且傳入當前時鐘(毫秒單位)
    // 如 10ms調用一次,或用 ikcp_check確定下次調用 update的時間不必每次調用
    ikcp_update(kcp, millisec);
  4. 輸入一個下層數據包:

    1
    2
    // 收到一個下層數據包(比如UDP包)時需要調用:
    ikcp_input(kcp, received_udp_packet, received_udp_size);

    處理了下層協議的輸出/輸入後 KCP協議就可以正常工作了,使用 ikcp_send 來向遠端發送數據。而另一端使用 ikcp_recv(kcp, ptr, size)來接收數據。

協議配置

協議默認模式是一個標準的 ARQ,需要通過配置打開各項加速開關:

  1. 工作模式:

    int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc)

    nodelay :是否啓用 nodelay模式,0不啓用;1啓用。interval :協議內部工作的 interval,單位毫秒,比如 10ms或者 20msresend :快速重傳模式,默認0關閉,可以設置2(2次ACK跨越將會直接重傳)nc :是否關閉流控,默認是0代表不關閉,1代表關閉。普通模式:`ikcp_nodelay(kcp, 0, 40, 0, 0);極速模式: ikcp_nodelay(kcp, 1, 10, 2, 1);

  2. 最大窗口:

    1
    int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);

    該調用將會設置協議的最大發送窗口和最大接收窗口大小,默認爲32.

  3. 最大傳輸單元:

    純算法協議並不負責探測 MTU,默認 mtu是1400字節,可以使用ikcp_setmtu來設置該值。該值將會影響數據包歸併及分片時候的最大傳輸單元。

  4. 最小RTO:

    不管是 TCP還是 KCP計算 RTO時都有最小 RTO的限制,即便計算出來RTO爲40ms,由於默認的 RTO是100ms,協議只有在100ms後才能檢測到丟包,快速模式下爲30ms,可以手動更改該值:

    1
    kcp->rx_minrto = 10;

最佳實踐

內存分配器

默認KCP協議使用 malloc/free進行內存分配釋放,如果應用層接管了內存分配,可以  用ikcp_allocator來設置新的內存分配器,注意要在一開始設置:

ikcp_allocator(my_new_malloc, my_new_free);

前向糾錯注意

爲了進一步提高傳輸速度,下層協議也許會使用前向糾錯技術。需要注意,前向糾錯會根據冗餘信息解出原始數據包。相同的原始數據包不要兩次input到KCP,否則將會導致kcp以爲對方重發了,這樣會產生更多的ack佔用額外帶寬。

比如下層協議使用最簡單的冗餘包:單個數據包除了自己外,還會重複存儲一次上一個數據包,以及上上一個數據包的內容:

Fn = (Pn, Pn-1, Pn-2)

P0 = (0, X, X)
P1 = (1, 0, X)
P2 = (2, 1, 0)
P3 = (3, 2, 1)

這樣幾個包發送出去,接收方對於單個原始包都可能被解出3次來(後面兩個包任然會重複該包內容),那麼這裏需要記錄一下,一個下層數據包只會input給kcp一次,避免過多重複ack帶來的浪費。

管理大規模連接

如果需要同時管理大規模的 KCP連接(比如大於3000個),比如你正在實現一套類 epoll的機制,那麼爲了避免每秒鐘對每個連接調用大量的調用 ikcp_update,我們可以使用ikcp_check來大大減少 ikcp_update調用的次數。 ikcp_check返回值會告訴你需要在什麼時間點再次調用 ikcp_update(如果中途沒有 ikcp_send, ikcp_input的話,否則中途調用了 ikcp_send, ikcp_input的話,需要在下一次interval時調用 update)

標準順序是每次調用了 ikcp_update後,使用 ikcp_check決定下次什麼時間點再次調用ikcp_update,而如果中途發生了 ikcp_send, ikcp_input的話,在下一輪 interval 立馬調用 ikcp_update和 ikcp_check。 使用該方法,原來在處理2000個 kcp連接且每個連接每10ms調用一次update,改爲 check機制後,cpu從 60%降低到 15%。

相關應用

  • dog-tunnel: GO開發的網絡隧道,使用 KCP極大的改進了傳輸速度,並移植了一份 GO版本 KCP
  • lua-kcp:KCP的 Lua擴展,用於 Lua服務器
  • asio-kcp: 使用 KCP的完整 UDP網絡庫,完整實現了基於 UDP的鏈接狀態管理,會話控制,KCP協議調度等
  • KCP協議開源地址:https://github.com/skywind3000/kcp

協議比較

如果永遠不丟包那麼 KCP和 TCP性能差不多,但網絡會卡,造成卡的原因就是丟包和抖動。在內網裏直接比較,大家都差不多,但是放到公網上,放到3G/4G網絡情況下,或者使用內網丟包模擬,差距就很明顯了。公網在高峯期有平均接近10%的丟包,wifi/3g/4g下更糟糕,這正是造成各種網絡卡頓的元兇。

感謝 asio-kcp 的作者 zhangyuan 對 KCP 與 enet, udt做過的一次橫向評測,結論如下:

  • ASIO-KCP has good performace in wifi and phone network(3G, 4G).
  • Extra using 20% ~ 50% network flow for speed improvement.
  • The kcp is the first choice for realtime pvp game.
  • The lag is less than 1 second when network lag happen. 3 times better than enet when lag happen.
  • The enet is a good choice if your game allow 2 second lag.
  • UDT is a bad idea. It always sink into badly situation of more than serval seconds lag. And the recovery is not expected.
  • enet has the problem of lack of doc. And it has lots of functions that you may intrest.
  • kcp's doc is chinese. Good thing is the function detail which is writen in code is english. And you can use asio_kcp which is a good wrap.
  • The kcp is a simple thing. You will write more code if you want more feature.
  • UDT has a perfect doc. UDT may has more bug than others as I feeling.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章