UDP可靠傳輸協(xié)議KCP的一些理解
UDP主要用在哪兩個方面
-
游戲
-
音視頻通話
為什么要使用UDP?
實時性的考慮,丟包重傳,TCP協(xié)議棧重傳無法控制,UDP重發(fā)可以自定義策略。
在DNS查詢的時候,也使用UDP,對資源的考慮。
如何做到可靠性連接?
-
ack機制
-
重傳機制 重傳策略
-
序號機制
-
重排機制
-
窗口機制
TCP和UDP的選擇
保證TCP的自動重傳請求 ARQ協(xié)議
ARQ協(xié)議(Automatic Repeat-reQuest),即自動重傳請求,是傳輸層的錯誤糾正協(xié) 議之一,它通過使用確認和超時兩個機制,在不可靠的網(wǎng)絡(luò)上實現(xiàn)可靠的信息 傳輸。 ARQ協(xié)議主要有3種模式:
-
即停等式(stop-and-wait)ARQ
-
回退n幀(go-back-n)ARQ,
-
選擇性重傳(selective repeat)ARQ
ARQ協(xié)議-停等式
停等協(xié)議的工作原理如下:
1、發(fā)送方對接收方發(fā)送數(shù)據(jù)包,然后等待接收方回復ACK并且開始計時。
2、在等待過程中,發(fā)送方停止發(fā)送新的數(shù)據(jù)包。
3、當數(shù)據(jù)包沒有成功被接收方接收,接收方不會發(fā)送ACK.這樣發(fā)送方在等待一 定時間后,重新發(fā)送數(shù)據(jù)包。
4、反復以上步驟直到收到從接收方發(fā)送的ACK.
缺點:較長的等待時間導致低的數(shù)據(jù)傳輸速度。
ARQ協(xié)議-回退n幀(go-back-n)ARQ
為了克服停等協(xié)議長時間等待ACK的缺陷,連續(xù)ARQ協(xié)議會連續(xù)發(fā)送一組數(shù)據(jù)包,然后再 等待這些數(shù)據(jù)包的ACK。
什么是滑動窗口:發(fā)送方和接收方都會維護一個數(shù)據(jù)幀的序列,這個序列被稱作窗口。發(fā)送方的 窗口大小由接收方確定,目的在于控制發(fā)送速度,以免接收方的緩存不夠大,而導致溢出,同時控 制流量也可以避免網(wǎng)絡(luò)擁塞。協(xié)議中規(guī)定,對于窗口內(nèi)未經(jīng)確認的分組需要重傳。
回退N步(Go-Back-N,GBN):回退N步協(xié)議允許發(fā)送方在等待超時的間歇,可以繼續(xù)發(fā)送分 組。所有發(fā)送的分組,都帶有序號。在GBN協(xié)議中,發(fā)送方需響應(yīng)以下三種事件:
1、上層的調(diào)用。上層調(diào)用相應(yīng)send()時,發(fā)送方首先要檢查發(fā)送窗口是否已滿。
2、接收ACK。在該協(xié)議中,對序號為n的分組的確認采取累積確認的方式,表明接收方已 正確接收到序號n以前(包括n)的所有分組。
3、超時。若出現(xiàn)超時,發(fā)送方將重傳所有已發(fā)出但還未被確認的分組
對于接收方來說,若一個序號為n的分組被正確接收,并且按序,則接收方會為該分組返 回一個ACK給發(fā)送方,并將該分組中的數(shù)據(jù)交付給上層。在其他情況下,接收方都會丟棄 分組。若分組n已接收并交付,那么所有序號比n小的分組也已完成了交付。因此GBN采用 累積確認是一個很自然的選擇。發(fā)送方在發(fā)完一個窗口里的所有分組后,會檢查最大的有 效確認,然后從最大有效確認的后一個分組開始重傳。
如上圖所示,序號為2的分組丟失,因此分組2及之后的分組都將被重傳。
總結(jié):GBN采用的技術(shù)包括序號、累積確認、檢驗和以及計時/重傳
ARQ協(xié)議-選擇重傳
雖然GBN改善了停等協(xié)議中時間等待較長的缺陷,但它依舊存在著性能問題。特別 是當窗口長度很大的時候,會使效率大大降低。而SR協(xié)議通過讓發(fā)送方僅重傳在接 收方丟失或損壞了的分組,從而避免了不必要的重傳,提高了效率。
在SR協(xié)議下,發(fā)送方需響應(yīng)以下三種事件:
1、從上層收到數(shù)據(jù)。當從上層收到數(shù)據(jù)后,發(fā)送方需檢查下一個可用于該分組的 序號。若序號在窗口中則將數(shù)據(jù)發(fā)送。
2、接收ACK。若收到ACK,且該分組在窗口內(nèi),則發(fā)送方將那個被確認的分組標記 為已接收。若該分組序號等于基序號,則窗口序號向前移動到具有最小序號的未確 認分組處。若窗口移動后并且有序號落在窗口內(nèi)的未發(fā)送分組,則發(fā)送這些分組。
3、超時。若出現(xiàn)超時,發(fā)送方將重傳已發(fā)出但還未確認的分組。與GBN不同的是, SR協(xié)議中的每個分組都有獨立的計時器
在SR協(xié)議下,接收方需響應(yīng)以下三種事件: (假設(shè)接收窗口的基序號為4,分組長度也為4) 1、序號在[4,7]內(nèi)的分組被正確接收。該情況下,收到的分組落在接收方的窗口內(nèi),一個ACK 將發(fā)送給發(fā)送方。若該分組是以前沒收到的分組,則被緩存。若該分組的序號等于基序號4, 則該分組以及以前緩存的序號連續(xù)的分組都交付給上層,然后,接收窗口將向前移動。
2、序號在[0,3]內(nèi)的分組被正確接收。在該情況下,必須產(chǎn)生一個ACK,盡管該分組是接收方 以前已確認過的分組。若接收方不確認該分組,發(fā)送方窗口將不能向前移動。
3、其他情況。忽略該分組 對于接收方來說,若一個分組正確接收而不管其是否按序,則接收方會為該分組返回一個ACK 給發(fā)送方。失序的分組將被緩存,直到所有丟失的分組都被收到,這時才可以將一批分組按 序交付給上層
RTT和RTO
- RTO(Retransmission TimeOut)即重傳超時時間。
- RTT(Round-Trip Time): 往返時延。表示從發(fā)送端發(fā)送數(shù)據(jù)開始,到發(fā)送端收到來自 接收端的確認(接收端收到數(shù)據(jù)后便立即發(fā)送確認),總共經(jīng)歷的時延。
由三部分組成:
* 鏈路的傳播時間(propagation delay)
* 末端系統(tǒng)的處理時間、
* 路由器緩存中的排隊和處理時間(queuing delay)
其中,前兩個部分的值對于一個TCP連接相對固定,路由器緩存中的排隊和處理時間會 隨著整個網(wǎng)絡(luò)擁塞程度的變化而變化。 所以RTT的變化在一定程度上反應(yīng)網(wǎng)絡(luò)的擁塞程 度。
在TCP的選項 內(nèi)容可以插入時間戳。
流量控制
- 雙方在通信的時候,發(fā)送方的速率與接收方的速率是不一定相等,如果發(fā)送方的發(fā)送速率太快,會導致接收方處理不過來,這時候接收方只能把處理不過來的數(shù)據(jù)存在緩存區(qū)里(失序的數(shù)據(jù)包也會被存放在緩存區(qū)里)。
- 如果緩存區(qū)滿了發(fā)送方還在瘋狂著發(fā)送數(shù)據(jù),接收方只能把收到的數(shù)據(jù)包丟掉,大量的丟包會極大著浪費網(wǎng)絡(luò)資源,因此,我們需要控制發(fā)送方的發(fā)送速率,讓接收方與發(fā)送方處于一種動態(tài)平衡才好。
- 對發(fā)送方發(fā)送速率的控制,稱之為流量控制。
如何控制?
-
接收方每次收到數(shù)據(jù)包,可以在發(fā)送確定報文的時候,同時告訴發(fā)送方自己的緩存區(qū)還剩
余多少是空閑的,我們也把緩存區(qū)的剩余大小稱之為接收窗口大小,用變量win來表示接收窗口的大小。 -
發(fā)送方收到之后,便會調(diào)整自己的發(fā)送速率,也就是調(diào)整自己發(fā)送窗口的大小,當發(fā)送方收到接收窗口的大小為0時,發(fā)送方就會停止發(fā)送數(shù)據(jù),防止出現(xiàn)大量丟包情況的發(fā)生。

發(fā)送方何時再繼續(xù)發(fā)送數(shù)據(jù)?
當發(fā)送方停止發(fā)送數(shù)據(jù)后,該怎樣才能知道自己可以繼續(xù)發(fā)送數(shù)據(jù)?
- 當接收方處理好數(shù)據(jù),接受窗口 win > 0 時,接收方發(fā)個通知報文去通知發(fā)送方,告訴他可以繼續(xù)發(fā)送數(shù)據(jù)了。當發(fā)送方收到窗口大于0的報文時,就繼續(xù)發(fā)送數(shù)據(jù)。
- 當發(fā)送方收到接受窗口 win = 0 時,這時發(fā)送方停止發(fā)送報文,并且同時開啟一個定時器,每隔一段時間就發(fā)個測試報文去詢問接收方,打聽是否可以繼續(xù)發(fā)送數(shù)據(jù)了,如果可以,接收方就告訴他此時接受窗口的大小;如果接受窗口大小還是為0,則發(fā)送方再次刷新啟動定時器。
![]()
小結(jié)
- 通信的雙方都擁有兩個滑動窗口,一個用于接受數(shù)據(jù),稱之為接收窗口;一個用于發(fā)送數(shù)據(jù),稱之為擁塞窗口(即發(fā)送窗口)。指出接受窗口大小的通知我們稱之為窗口通告。
- 接收窗口的大小固定嗎?接受窗口的大小是根據(jù)某種算法動態(tài)調(diào)整的。
- 接收窗口越大越好嗎?當接收窗口達到某個值的時候,再增大的話也不怎么會減少丟包率的了,而且還會更加消耗內(nèi)存。所以接收窗口的大小必須根據(jù)網(wǎng)絡(luò)環(huán)境以及發(fā)送發(fā)的的擁塞窗口來動態(tài)調(diào)整.
- 發(fā)送窗口和接受窗口相等嗎?接收方在發(fā)送確認報文的時候,會告訴發(fā)送發(fā)自己的接收窗口大小,而發(fā)送方的發(fā)送窗口會據(jù)此來設(shè)置自己的發(fā)送窗口,但這并不意味著他們就會相等。首先接收方把確認報文發(fā)出去的那一刻,就已經(jīng)在一邊處理堆在自己緩存區(qū)的數(shù)據(jù)了,所以一般情況下接收窗口 >= 發(fā)送窗口。
2.5 阻塞控制

阻塞控制和流量控制雖然采取的動作很相似,但擁塞控制與網(wǎng)絡(luò)的擁堵情況相關(guān)聯(lián),而流量控制與接收方的緩存狀態(tài)相關(guān)聯(lián)。
2.6 UDP并發(fā)編程
源碼路徑:
https://github.com/wangbojing/udp_server_concurrent
UDP如何可靠,KCP協(xié)議在哪些方面有優(yōu)勢
以10%-20%帶寬浪費的代價換取了比 TCP快30%-40%的傳輸速度。
RTO翻倍vs不翻倍:
TCP超時計算是RTOx2,這樣連續(xù)丟三次包就變成RTOx8了,十分恐怖,而KCP啟動快速模式后不x2,只是x1.5(實驗證明1.5這個值相對比較好),提高了傳輸速度。
以RTO=100ms為例:

選擇性重傳 vs 全部重傳:
TCP丟包時會全部重傳從丟的那個包開始以后的數(shù)據(jù),KCP是選擇性重傳,只重傳真正丟失的數(shù)據(jù)包。
快速重傳(跳過多少個包馬上重傳)(如果使用了快速重傳,可以不考慮RTO)):
發(fā)送端發(fā)送了1,2,3,4,5幾個包,然后收到遠端的ACK: 1, 3, 4, 5,當收到ACK3時,KCP
知道2被跳過1次,收到ACK4時,知道2被跳過了2次,此時可以認為2號丟失,不用等超時,直接重傳2號包,大大改善了丟包時的傳輸速度。 fastresend =2
延遲ACK vs 非延遲ACK:
TCP為了充分利用帶寬,延遲發(fā)送ACK(NODELAY都沒用),這樣超時計算會算出較大RTT時間,延長了丟包時的判斷過程。KCP的ACK是否延遲發(fā)送可以調(diào)節(jié)。
UNA vs ACK+UNA:
ARQ模型響應(yīng)有兩種,UNA(此編號前所有包已收到,如TCP)和ACK(該編號包已收到),光用UNA將導致全部重傳,光用ACK則丟失成本太高,以往協(xié)議都是二選其一,而 KCP協(xié)議中,除去單獨的 ACK包外,所有包都有UNA信息。
非退讓流控:
KCP正常模式同TCP一樣使用公平退讓法則,即發(fā)送窗口大小由:發(fā)送緩存大小、接收端剩余接收緩存大小、丟包退讓及慢啟動這四要素決定。但傳送及時性要求很高的小數(shù)據(jù)時,可選擇通過配置跳過后兩步,僅用前兩項來控制發(fā)送頻率。以犧牲部分公平性及帶寬利用率之代價,換取了開著BT都能流暢傳輸?shù)男Ч?/p>
KCP精講-名詞說明
- kcp官方:https://github.com/skywind3000/kcp
- 名詞說明
用戶數(shù)據(jù):應(yīng)用層發(fā)送的數(shù)據(jù),如一張圖片2Kb的數(shù)據(jù)
MTU:最大傳輸單元。即每次發(fā)送的最大數(shù)據(jù),1500 實際使用1400
RTO:Retransmission TimeOut,重傳超時時間。
cwnd: congestion window,擁塞窗口,表示發(fā)送方可發(fā)送多少個KCP數(shù)據(jù)包。與接
收方窗口有關(guān),與網(wǎng)絡(luò)狀況(擁塞控制)有關(guān),與發(fā)送窗口大小有關(guān)。
rwnd: receiver window,接收方窗口大小,表示接收方還可接收多少個KCP數(shù)據(jù)包
snd_queue: 待發(fā)送KCP數(shù)據(jù)包隊列
snd_nxt:下一個即將發(fā)送的kcp數(shù)據(jù)包序列號
snd_una:下一個待確認的序列號,即是之前的包接收端都已經(jīng)收到。
kcp使用方式
- 創(chuàng)建 KCP對象:ikcpcb *kcp = ikcp_create(conv, user);
- 設(shè)置發(fā)送回調(diào)函數(shù)(如UDP的send函數(shù)):kcp->output = udp_output;
- 真正發(fā)送數(shù)據(jù)需要調(diào)用sendto
- 循環(huán)調(diào)用 update:ikcp_update(kcp, millisec);
- 輸入一個應(yīng)用層數(shù)據(jù)包(如UDP收到的數(shù)據(jù)包):kcp_input(kcp,received_udp_packet,received_udp_size);
- 我們要使用recvfrom接收,然后扔到kcp里面做解析
- 發(fā)送數(shù)據(jù):ikcp_send(kcp1, buffer, 8); 用戶層接口
- 接收數(shù)據(jù):hr = ikcp_recv(kcp2, buffer, 10)
kcp源碼流程圖

kcp配置模式
-
工作模式:int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc)
? nodelay :是否啟用 nodelay模式,0不啟用;1啟用。
? interval :協(xié)議內(nèi)部工作的 interval,單位毫秒,比如 10ms或者 20ms
? resend :快速重傳模式,默認0關(guān)閉,可以設(shè)置2(2次ACK跨越將會直接重傳)
? nc :是否關(guān)閉流控,默認是0代表不關(guān)閉,1代表關(guān)閉。
默認模式:ikcp_nodelay(kcp, 0, 10, 0, 0);
普通模式: ikcp_nodelay(kcp, 0, 10, 0, 1);關(guān)閉流控等
極速模式: ikcp_nodelay(kcp, 2, 10, 2, 1),并且修改kcp1->rx_minrto = 10;kcp1->fastresend = 1; -
最大窗口:int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
該調(diào)用將會設(shè)置協(xié)議的最大發(fā)送窗口和最大接收窗口大小,默認為32,單位為包。 -
最大傳輸單元:int ikcp_setmtu(ikcpcb *kcp, int mtu);
kcp協(xié)議并不負責探測 MTU,默認 mtu是1400字節(jié) -
最小RTO:不管是 TCP還是 KCP計算 RTO時都有最小 RTO的限制,即便計算出來RTO為
40ms,由于默認的 RTO是100ms,協(xié)議只有在100ms后才能檢測到丟包,快速模式下為30ms,可以手動更改該值: kcp->rx_minrto = 10;
kcp協(xié)議頭

- [0,3]conv:連接號。UDP是無連接的,conv用于表示來自于哪個客戶端。對連接的一種替代
- [4]cmd:命令字。如,IKCP_CMD_ACK確認命令,IKCP_CMD_WASK接收窗口大小詢問命令,IKCP_CMD_WINS接收窗口大小告知命令,
- [5]frg:分片,用戶數(shù)據(jù)可能會被分成多個KCP包,發(fā)送出去
- [6,7]wnd:接收窗口大小,發(fā)送方的發(fā)送窗口不能超過接收方給出的數(shù)值
- [8,11]ts:時間序列
- [12,15]sn:序列號
- [16,19]una:下一個可接收的序列號。其實就是確認號,收到sn=10的包,una為11
- [20,23]len:數(shù)據(jù)長度
- data:用戶數(shù)據(jù),這一次發(fā)送的數(shù)據(jù)長度
| cmd | 作用 |
|---|---|
| IKCP_CMD_PUSH | 數(shù)據(jù)推送命令 |
| IKCP_CMD_ACK | 確認命令 |
| IKCP_CMD_WASK | 接收窗口大小詢問命令 |
| IKCP_CMD_WINS | 接收窗口大小告知命令 |
| IKCP_CMD_PUSH和IKCP_CMD_ACK 關(guān)聯(lián) | |
| IKCP_CMD_WASK和IKCP_CMD_WINS 關(guān)聯(lián) | |
| IKCP_CMD_ACK 是確認單獨的包。 |
kcp發(fā)送數(shù)據(jù)過程



接收窗口
snd_wnd:固定大小,默認32
rmt_wnd:遠端接收窗口大小,默認32
cwnd:滑動窗口,可變,越小一次能發(fā)送的數(shù)據(jù)越小
接收窗口的控制是:recv_queue的接收能力,比如默認接收端口為32,如果recv_queue接收了32個包后則接收窗口為0,然后用戶讀走了32個包,則接收窗口變?yōu)?2。
IKCP_CMD_PUSH 命令

kcp確認包處理流程

kcp快速確認

ikcp_input邏輯

應(yīng)答列表acklist
- 收到包后將序號,時間戳存儲到應(yīng)答列表。
在ikcp_input函數(shù)調(diào)用ikcp_ack_push存儲應(yīng)答包ikcp_ack_push(kcp, sn, ts); // 對該報文的確認 ACK 報文放入 ACK 列表中 - 發(fā)送應(yīng)答包
在ikcp_flush函數(shù)發(fā)送應(yīng)答包 - 應(yīng)答包解析
在ikcp_input函數(shù)進行解析,判斷IKCP_CMD_ACK

流量控制和擁塞控制
RTO計算(與TCP完全一樣)
RTT:一個報文段發(fā)送出去,到收到對應(yīng)確認包的時間差。
SRTT(kcp->rx_srtt):RTT的一個加權(quán)RTT平均值,平滑值。
RTTVAR(kcp->rx_rttval):RTT的平均偏差,用來衡量RTT的抖動。
探測對方接收窗口
ikcp_flush 發(fā)送探測窗口IKCP_CMD_WASK

ikcp_input函數(shù)
cmd == IKCP_CMD_WASK,標記kcp->probe |= IKCP_ASK_TELL;
ikcp_flush 回應(yīng)探測IKCP_CMD_WINS

推薦一個零聲學院免費教程,個人覺得老師講得不錯,
分享給大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,
TCP/IP,協(xié)程,DPDK等技術(shù)內(nèi)容,點擊立即學習:
服務(wù)器
音視頻
dpdk
Linux內(nèi)核


浙公網(wǎng)安備 33010602011771號