WeTest 導讀html
雲真機已經支持手機端的畫面投影。雲真機實時操做,對延遲的要求比遠程視頻對話的要求更高(100ms之內)。在無線網絡下,如何更實時、更可靠的傳輸視頻流就成了一個挑戰。經過websocket、RTMP、UDP的比較,最後選擇了可靠的UDP協議KCP來進行實時音視頻的傳輸。node
1 簡介linux
KCP是一個快速可靠協議,能以比 TCP浪費10%-20%的帶寬的代價,換取平均延遲下降 30%-40%,且最大延遲下降三倍的傳輸效果。純算法實現,並不負責底層協議(如UDP)的收發,須要使用者本身定義下層數據包的發送方式,以 callback的方式提供給 KCP。 連時鐘都須要外部傳遞進來,內部不會有任何一次系統調用。本文傳輸協議之考慮UDP的狀況。android
名詞說明(源碼字段):
用戶數據:應用層發送的數據,如一張圖片2Kb的數據
MTU:最大傳輸單元。即每次發送的最大數據
RTO:Retransmission TimeOut,重傳超時時間。
cwnd:congestion window,擁塞窗口,表示發送方可發送多少個KCP數據包。與接收方窗口有關,與網絡情況(擁塞控制)有關,與發送窗口大小有關。
rwnd:receiver window,接收方窗口大小,表示接收方還可接收多少個KCP數據包
snd_queue:待發送KCP數據包隊列
snd_nxt:下一個即將發送的kcp數據包序列號
snd_una:下一個待確認的序列號web
1.1 使用方式算法
1. 建立 KCP對象:緩存
// 初始化 kcp對象,conv爲一個表示會話編號的整數,和tcp的 conv同樣,通訊雙websocket
// 方需保證 conv相同,相互的數據包纔可以被承認,user是一個給回調函數的指針網絡
ikcpcb *kcp = ikcp_create(conv, user);socket
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數據包,超時重傳、快速重傳、選擇重傳。
一、超時重傳
TCP超時計算是RTOx2,這樣連續丟三次包就變成RTOx8了,而KCP非快速模式下每次+RTO,急速模式下+0.5RTO(實驗證實1.5這個值相對比較好),提升了傳輸速度。
[ RTO算法對比圖 ]
二、快速重傳
發送端發送了1,2,3,4,5幾個包,而後收到遠端的ACK: 1, 3, 4, 5,當收到ACK3時,KCP知道2被跳過1次,收到ACK4時,知道2被跳過了2次,此時能夠認爲2號丟失,不用等超時,直接重傳2號包,大大改善了丟包時的傳輸速度。TCP有快速重傳算法,TCP包被跳過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更新接收端接收窗口大小。根據不一樣的命令字進行分別處理。數據接收後,更新流程以下所示:
[ 接收處理流程圖 ]
一、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包數據進行組合返回給用戶。
二、IKCP_CMD_ACK數據確認包
兩個使命:一、RTO更新,二、確認發送包接收方已接收到。
正常狀況:收到的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