可靠UDP,KCP協議快在哪?


WeTest 導讀

雲真機已經支持手機端的畫面投影。雲真機實時操作,對延遲的要求比遠程視頻對話的要求更高(100ms以內)。在無線網絡下,如何更實時、更可靠的傳輸視頻流就成了一個挑戰。通過websocket、RTMP、UDP的比較,最后選擇了可靠的UDP協議KCP來進行實時音視頻的傳輸。

 


 

1 簡介

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

 

名詞說明(源碼字段):
用戶數據:應用層發送的數據,如一張圖片2Kb的數據
MTU:最大傳輸單元。即每次發送的最大數據
RTO:Retransmission TimeOut,重傳超時時間。
cwnd:congestion window,擁塞窗口,表示發送方可發送多少個KCP數據包。與接收方窗口有關,與網絡狀況(擁塞控制)有關,與發送窗口大小有關。
rwnd:receiver window,接收方窗口大小,表示接收方還可接收多少個KCP數據包
snd_queue:待發送KCP數據包隊列
snd_nxt:下一個即將發送的kcp數據包序列號
snd_una:下一個待確認的序列號

 

1.1 使用方式

1. 創建 KCP對象:

// 初始化 kcp對象,conv為一個表示會話編號的整數,和tcp的 conv一樣,通信雙

// 方需保證 conv相同,相互的數據包才能夠被認可,user是一個給回調函數的指針

ikcpcb *kcp = ikcp_create(conv, user);

2. 設置傳輸回調函數(如UDP的send函數):

// 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:

// 以一定頻率調用 ikcp_update來更新 kcp狀態,並且傳入當前時鍾(毫秒單位)

// 如 10ms調用一次,或用 ikcp_check確定下次調用 update的時間不必每次調用

ikcp_update(kcp, millisec);

4. 輸入一個應用層數據包(如UDP收到的數據包):

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

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

[ kcp源碼流程圖 ]

 

 

總結:UDP收到的包,不斷通過kcp_input喂給KCP,KCP會對這部分數據(KCP協議數據)進行解包,重新封裝成應用層用戶數據,應用層通過kcp_recv獲取。應用層通過kcp_send發送數據,KCP會把用戶數據拆分kcp數據包,通過kcp_output,以UDP(send)的方式發送。

 

1.2 KCP的配置模式

這部分KCP文檔有介紹,理解KCP協議無需過於關注。協議默認模式是一個標准的 ARQ,需要通過配置打開各項加速開關:

 

1. 工作模式:

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

  • nodelay :是否啟用 nodelay模式,0不啟用;1啟用。

  • interval :協議內部工作的 interval,單位毫秒,比如 10ms或者 20ms

  • resend :快速重傳模式,默認0關閉,可以設置2(2次ACK跨越將會直接重傳)

  • nc :是否關閉流控,默認是0代表不關閉,1代表關閉。

     

 

普通模式: ikcp_nodelay(kcp, 0, 40, 0, 0);

極速模式: ikcp_nodelay(kcp, 1, 10, 2, 1)

 

1. 最大窗口

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

該調用將會設置協議的最大發送窗口和最大接收窗口大小,默認為32. 這個可以理解為 TCP的 SND_BUF 和 RCV_BUF,只不過單位不一樣 SND/RCV_BUF 單位是字節,這個單位是包。

 

2. 最大傳輸單元:

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

 

3. 最小RTO:

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

 

1.3 KCP為什么存在?

首先要看TCP與UDP的區別,TCP與UDP都是傳輸層的協議,比較兩者的區別主要應該是說TCP比UDP多了什么?


面向連接:TCP接收方與發送方維持了一個狀態(建立連接,斷開連接),雙方知道對方還在。
可靠的:發送出去的數據對方一定能夠接收到,而且是按照發送的順序收到的。
流量控制與擁塞控制:TCP靠譜通過滑動窗口確保,發送的數據接收方來得及收。TCP無私,發生數據包丟失的時候認為整個網絡比較堵,自己放慢數據發送速度。


TCP協議的可靠與無私讓使用TCP開發更為簡單,同時它的這種設計也導致了慢的特點。UDP協議簡單,所以它更快。但是,UDP畢竟是不可靠的,應用層收到的數據可能是缺失、亂序的。KCP協議就是在保留UDP快的基礎上,提供可靠的傳輸,應用層使用更加簡單。

 

其他差別,TCP以字節流的形式,UDP以數據包的形式。很多人以為,UDP是不可靠的,所以sendto(1000),接收端recvfrom(1000)可能會收到900。這個是錯誤的。所謂數據包,就是說UDP是有界的,sendto(300),sendto(500);接收到,recvfrom(1000),recvfrom(1000)那么可能會收到300,500或者其中一個或者都沒收到。UDP應用層發送的數據,在接收緩存足夠的情況下,要么收到全的,要么收不到。

 

總結:TCP可靠簡單,但是復雜無私,所以速度慢。KCP盡可能保留UDP快的特點下,保證可靠。

 

2 KCP原理

2.1 協議簡介

KCP是一個可靠的傳輸協議,UDP本身是不可靠的,所以需要額外信息來保證傳輸數據的可靠性。因此,我們需要在傳輸的數據上增加一個包頭。用於確保數據的可靠、有序。

0                         4      5     6      8 (BYTE)

+-------------------+----+----+----+ 

|      conv      | cmd | frg |  wnd | 

+-------------------+----+----+----+   8

|         ts         |             sn           | 

+-------------------+----------------+  16

|       una       |             len          |

+-------------------+----------------+   24

|                                                   |

|            DATA (optional)          |

|                                                   |

+-------------------------------------+

conv:連接號。UDP是無連接的,conv用於表示來自於哪個客戶端。對連接的一種替代
cmd:命令字。如,IKCP_CMD_ACK確認命令,IKCP_CMD_WASK接收窗口大小詢問命令,IKCP_CMD_WINS接收窗口大小告知命令,
frg:分片,用戶數據可能會被分成多個KCP包,發送出去
wnd:接收窗口大小,發送方的發送窗口不能超過接收方給出的數值
ts:時間序列
sn:序列號
una:下一個可接收的序列號。其實就是確認號,收到sn=10的包,una為11
len:數據長度
data:用戶數據

 

后面的講解,主要以極速模式: ikcp_nodelay(kcp, 1, 10, 2, 1)為主,啟用nodelay設置,刷新間隔控制在10ms,開啟快速重傳模式,關閉流量控制。

 

2.2 數據發送過程

2.2.1 數據發送准備

用戶發送數據的函數為ikcp_send。
ikcp_send(ikcpcb kcp, const char buffer, int len)
該函數的功能非常簡單,把用戶發送的數據根據MSS進行分片。如上圖,用戶發送1900字節的數據,MTU為1400byte。因此,該函數會把1900byte的用戶數據分成兩個包,一個數據大小為1400,頭frg設置為1,len設置為1400;第二個包,頭frg設置為0,len設置為500。切好KCP包之后,放入到名為snd_queue的待發送隊列中。

注:流模式情況下,kcp會把兩次發送的數據銜接為一個完整的kcp包。非流模式下,用戶數據%MSS的包,也會作為一個包發送出去。

MTU,數據鏈路層規定的每一幀的最大長度,超過這個長度數據會被分片。通常MTU的長度為1500字節,IP協議規定所有的路由器均應該能夠轉發(512數據+60IP首部+4預留=576字節)的數據。MSS,最大輸出大小(雙方的約定),KCP的大小為MTU-kcp頭24字節。IP數據報越短,路由器轉發越快,但是資源利用率越低。傳輸鏈路上的所有MTU都一至的情況下效率最高,應該盡可能的避免數據傳輸的工程中,再次被分。UDP再次被分的后(通常1分為2),只要丟失其中的任意一份,兩份都要重新傳輸。因此,合理的MTU應該是保證數據不被再分的前提下,盡可能的大。
以太網的MTU通常為1500字節-IP頭(20字節固定+40字節可選)-UDP頭8個字節=1472字節。KCP會考慮多傳輸協議,但是在UDP的情況下,設置為1472字節更為合理。

 

2.2.2 實際發送

KCP會不停的進行update更新最新情況,數據的實際發送在update時進行。發送過程如下圖所示:

[ KCP 發送過程 ]

 

步驟1:待發送隊列移至發送隊列
KCP會把snd_queue待發送隊列中的kcp包,移至snd_buf發送隊列。移動的包的數量不會超過snd_una+cwnd-snd_nxt,確保發送的數據不會讓接收方的接收隊列溢出。該功能類似於TCP協議中的滑動窗口。cwnd=min(snd_wnd,rmt_wnd,kcp->cwnd)的最小值決定,snd_wnd,rmt_wnd比較好理解可發送的數據,可發送的數據最大值,應該是發送方可以發送的數據和接收方可以接收的數據的最小值。kcp->cwnd是擁塞控制的一個值,跟網絡狀況相關,網絡狀況差的時候,KCP認為應該降低發送的數據,后面會有詳細的介紹。
如上圖中,snd_queue待發送隊列中有4個KCP包等待發送,這個時候snd_nxt下一個發送的kcp包序列號為11,snd_una下一個確認的KCP包為9(8已經確認,9,10已經發送但是還沒得到接收方的確認)。因為cwnd=5,發送隊列中還有2個發送了但是還未得到確認,所以可以從待發送隊列中取前面的3個KCP包放入到發送隊列中,序列號分別設置為11,12,13。

 

步驟2:發送發送隊列的數據
發送隊列中包含兩種類型的數據,已發送但是尚未被接收方確認的數據,沒被發送過的數據。沒發送過的數據比較好處理,直接發送即可。重點在於已經發送了但是還沒被接收方確認的數據,該部分的策略直接決定着協議快速、高效與否。KCP主要使用兩種策略來決定是否需要重傳KCP數據包,超時重傳、快速重傳、選擇重傳。

 

1、超時重傳
TCP超時計算是RTOx2,這樣連續丟三次包就變成RTOx8了,而KCP非快速模式下每次+RTO,急速模式下+0.5RTO(實驗證明1.5這個值相對比較好),提高了傳輸速度。

 

[ RTO算法對比圖 ]

 

2、快速重傳
發送端發送了1,2,3,4,5幾個包,然后收到遠端的ACK: 1, 3, 4, 5,當收到ACK3時,KCP知道2被跳過1次,收到ACK4時,知道2被跳過了2次,此時可以認為2號丟失,不用等超時,直接重傳2號包,大大改善了丟包時的傳輸速度。TCP有快速重傳算法,TCP包被跳過3次之后會進行重傳。
注:可以通過統計錯誤重傳(重傳的包實際沒丟,僅亂序),優化該設置。

 

3、選擇重傳
老的TCP丟包時會全部重傳從丟的那個包開始以后的數據,KCP是選擇性重傳,只重傳真正丟失的數據包。但是,目前大部分的操作系統,linux與android手機均是支持SACK選擇重傳的。

 

步驟3:數據發送
通過步驟2判定,kcp包是否需要發送,如果需要發送的kcp包則通過,kcp_setoutput設置的發送接口進行發送,UDP通常為sendto。步驟3,會對較小的kcp包進行合並,一次性發送提高效率

 

2.3 數據接收過程

KCP的接收過程是將UDP收到的數據進行解包,重新組裝順序的、可靠的數據后交付給用戶。

 

2.3.1 KCP數據包接收

 

kcp_input輸入UDP收到的數據包。kcp包對前面的24個字節進行解壓,包括conv、 frg、 cmd、 wnd、 ts、 sn、 una、 len。根據una,會刪除snd_buf中,所有una之前的kcp數據包,因為這些數據包接收者已經確認。根據wnd更新接收端接收窗口大小。根據不同的命令字進行分別處理。數據接收后,更新流程如下所示:

[ 接收處理流程圖 ]

 

1、IKCP_CMD_PUSH數據發送命令
a、KCP會把收到的數據包的sn及ts放置在acklist中,兩個相鄰的節點為一組,分別存儲sn和ts。update時會讀取acklist,並以IKCP_CMD_ACK的命令返回確認包。如下圖中,收到了兩個kpc包,acklist中會分別存放10,123,11,124。
b、kcp數據包放置rcv_buf隊列。丟棄接收窗口之外的和重復的包。然后將rcv_buf中的包,移至rcv_queue。原來的rcv_buf中已經有sn=10和sn=13的包了,sn=10的kcp包已經在rcv_buf中了,因此新收到的包會直接丟棄掉,sn=11的包放置至rcv_buf中。
c、把rcv_buf中前面連續的數據sn=11,12,13全部移動至rcv_queue,rcv_nxt也變成14。

rcv_queue的數據是連續的,rcv_buf可能是間隔的
d、kcp_recv函數,用戶獲取接收到數據(去除kcp頭的用戶數據)。該函數根據frg,把kcp包數據進行組合返回給用戶。

 

2、IKCP_CMD_ACK數據確認包
兩個使命:1、RTO更新,2、確認發送包接收方已接收到。

 

正常情況:收到的sn為11,una為12。表示sn為11的已經確認,下一個等待接收的為12。發送隊列中,待確認的一個包為11,這個時候snd_una向后移動一位,序列號為11的包從發送隊列中刪除。

[ 數據確認包處理流程 ]

 

異常情況:如下圖所示,sn!=11的情況均為異常情況。sn<11表示,收到重復確認的包,如本來以為丟失的包重新又收到了,所以產生重復確認的包;sn>17,收到沒發送過的序列號,概率極低,可能是conv沒變重啟程序導致的;112,則啟動快速重傳

[ KCP快速確認 ]

 

確認包發送,接收到的包會全部放在acklist中,以IKCP_CMD_ACK包發送出去

 

3 流量控制與擁塞控制

 

3.1 RTO計算(與TCP完全一樣)

RTT:一個報文段發送出去,到收到對應確認包的時間差。
SRTT(kcp->rx_srtt):RTT的一個加權RTT平均值,平滑值。
RTTVAR(kcp->rx_rttval):RTT的平均偏差,用來衡量RTT的抖動。

 

3.2 流量控制

流量控制是點對點的通信量的控制,是一個端到端的問題。總結起來,就是發送方的速度要匹配接收方接收(處理)數據的速度。發送方要抑制自身的發送速率,以便使接收端來得及接收。


KCP的發送機制采用TCP的滑動窗口方式,可以非常容易的控制流量。KCP的頭中包含wnd,即接收方目前可以接收的大小。能夠發送的數據即為snd_una與snd_una+wnd之間的數據。接收方每次都會告訴發送方我還能接收多少,發送方就控制下,確保自己發送的數據不多於接收端可以接收的大小。

 

KCP默認為32,即可以接收最大為32*MTU=43.75kB。KCP采用update的方式,更新間隔為10ms,那么KCP限定了你最大傳輸速率為4375kB/s,在高網速傳輸大內容的情況下需要調用ikcp_wndsize調整接收與發送窗口。

 

KCP的主要特色在於實時性高,對於實時性高的應用,如果發生數據堆積會造成延遲的持續增大。建議從應用側更好的控制發送流量與網絡速度持平,避免緩存堆積延遲。(詳見參考資料)

 

3.3 擁塞控制(KCP可關閉)

KCP的優勢在於可以完全關閉擁塞控制,非常自私的進行發送。KCP采用的擁塞控制策略為TCP最古老的策略,無任何優勢。完全關閉擁塞控制,也不是一個最優策略,它全是會造成更為擁堵的情況。
網絡中鏈路的帶寬,與整條網絡中的交換節點(路由器、交換機、基站等)有關。如果,所有使用該鏈路的流量超出了,該鏈路所能提供的能力,就會發生擁塞。車多路窄,就會堵車,車越多堵的越厲害。因此,TCP作為一個大公無私的協議,當網絡上發送擁堵的時候會降低自身發送數據的速度。擁塞控制是整個網絡的事情,流量控制是發送和接收兩方的事情。
當發送方沒有按時接收到確認包,就認為網絡發生了擁堵行為。TCP擁塞控制的方式,歸結為慢開始、擁塞避免,如下圖所示

[ 擁塞控制算法 ]

 

KCP發生丟包的情況下的擁塞控制策略與TCP Tahoe版本的策略一致。TCP Reno版本已經使用快恢復策略。因此,丟包的情況下,其實KCP擁塞控制策略比TCP更為苛刻。

KCP在發生快速重傳,數據包亂序時,采用的是TCP快恢復的策略。控制窗口調整為已經發送沒有接收到ack的數據包數目的一半+resent。

注:目前kernel 3.2以上的linux,默認采用google改進的擁塞控制算法,Proportional Rate Reduction for TCP。該算法的主要特點為,的cwnd如下圖所示:

 

 

 

 

手機投射靜態演示

 


 

目前,“WeTest助手”的手機控制器已經上線,完美復制真機體驗,體驗暢快淋漓的操作!

 

點擊鏈接:http://wetest.qq.com/cloud/help/effective 即可下載“WeTest助手”。

 

 

 

如果使用當中有任何疑問,歡迎咨詢騰訊WeTest企業QQ:800024531

 

  騰訊WeTest有獎征文活動進行中,歡迎投稿!了解詳情:
http://wetest.qq.com/lab/view/379.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM