UDP可靠傳輸協議KCP的一些理解

UDP主要用在哪兩個方面

  • 遊戲

  • 音視頻通話

爲什麼要使用UDP?

實時性的考慮,丟包重傳,TCP協議棧重傳無法控制,UDP重發可以自定義策略。

在DNS查詢的時候,也使用UDP,對資源的考慮。

如何做到可靠性連接?

  • ack機制

  • 重傳機制 重傳策略

  • 序號機制

  • 重排機制

  • 窗口機制

TCP和UDP的選擇

保證TCP的自動重傳請求 ARQ協議

ARQ協議(Automatic Repeat-reQuest),即自動重傳請求,是傳輸層的錯誤糾正協 議之一,它通過使用確認和超時兩個機制,在不可靠的網絡上實現可靠的信息 傳輸。 ARQ協議主要有3種模式:

  1. 即停等式(stop-and-wait)ARQ

  2. 回退n幀(go-back-n)ARQ,

  3. 選擇性重傳(selective repeat)ARQ

ARQ協議-停等式

停等協議的工作原理如下:

1、發送方對接收方發送數據包,然後等待接收方回覆ACK並且開始計時。

2、在等待過程中,發送方停止發送新的數據包。

3、當數據包沒有成功被接收方接收,接收方不會發送ACK.這樣發送方在等待一 定時間後,重新發送數據包。

4、反覆以上步驟直到收到從接收方發送的ACK.

缺點:較長的等待時間導致低的數據傳輸速度。

ARQ協議-回退n幀(go-back-n)ARQ

爲了克服停等協議長時間等待ACK的缺陷,連續ARQ協議會連續發送一組數據包,然後再 等待這些數據包的ACK。

什麼是滑動窗口:發送方和接收方都會維護一個數據幀的序列,這個序列被稱作窗口。發送方的 窗口大小由接收方確定,目的在於控制發送速度,以免接收方的緩存不夠大,而導致溢出,同時控 制流量也可以避免網絡擁塞。協議中規定,對於窗口內未經確認的分組需要重傳。

回退N步(Go-Back-N,GBN):回退N步協議允許發送方在等待超時的間歇,可以繼續發送分 組。所有發送的分組,都帶有序號。在GBN協議中,發送方需響應以下三種事件:

1、上層的調用。上層調用相應send()時,發送方首先要檢查發送窗口是否已滿

2、接收ACK。在該協議中,對序號爲n的分組的確認採取累積確認的方式,表明接收方已 正確接收到序號n以前(包括n)的所有分組。

3、超時。若出現超時,發送方將重傳所有已發出但還未被確認的分組

對於接收方來說,若一個序號爲n的分組被正確接收,並且按序,則接收方會爲該分組返 回一個ACK給發送方,並將該分組中的數據交付給上層。在其他情況下,接收方都會丟棄 分組。若分組n已接收並交付,那麼所有序號比n小的分組也已完成了交付。因此GBN採用 累積確認是一個很自然的選擇。發送方在發完一個窗口裏的所有分組後,會檢查最大的有 效確認,然後從最大有效確認的後一個分組開始重傳。

如上圖所示,序號爲2的分組丟失,因此分組2及之後的分組都將被重傳

總結:GBN採用的技術包括序號、累積確認、檢驗和以及計時/重傳

ARQ協議-選擇重傳

雖然GBN改善了停等協議中時間等待較長的缺陷,但它依舊存在着性能問題。特別 是當窗口長度很大的時候,會使效率大大降低。而SR協議通過讓發送方僅重傳在接 收方丟失或損壞了的分組,從而避免了不必要的重傳,提高了效率。

在SR協議下,發送方需響應以下三種事件:

1、從上層收到數據。當從上層收到數據後,發送方需檢查下一個可用於該分組的 序號。若序號在窗口中則將數據發送。

2、接收ACK。若收到ACK,且該分組在窗口內,則發送方將那個被確認的分組標記 爲已接收。若該分組序號等於基序號,則窗口序號向前移動到具有最小序號的未確 認分組處。若窗口移動後並且有序號落在窗口內的未發送分組,則發送這些分組。

3、超時。若出現超時,發送方將重傳已發出但還未確認的分組。與GBN不同的是, SR協議中的每個分組都有獨立的計時器

在SR協議下,接收方需響應以下三種事件: (假設接收窗口的基序號爲4,分組長度也爲4) 1、序號在[4,7]內的分組被正確接收。該情況下,收到的分組落在接收方的窗口內,一個ACK 將發送給發送方。若該分組是以前沒收到的分組,則被緩存。若該分組的序號等於基序號4, 則該分組以及以前緩存的序號連續的分組都交付給上層,然後,接收窗口將向前移動。

2、序號在[0,3]內的分組被正確接收。在該情況下,必須產生一個ACK,儘管該分組是接收方 以前已確認過的分組。若接收方不確認該分組,發送方窗口將不能向前移動。

3、其他情況。忽略該分組 對於接收方來說,若一個分組正確接收而不管其是否按序,則接收方會爲該分組返回一個ACK 給發送方。失序的分組將被緩存,直到所有丟失的分組都被收到,這時纔可以將一批分組按 序交付給上層

RTT和RTO

  • RTO(Retransmission TimeOut)即重傳超時時間。
  • RTT(Round-Trip Time): 往返時延。表示從發送端發送數據開始,到發送端收到來自 接收端的確認(接收端收到數據後便立即發送確認),總共經歷的時延。
      由三部分組成:
      * 鏈路的傳播時間(propagation delay)
      * 末端系統的處理時間、
      * 路由器緩存中的排隊和處理時間(queuing delay)
      其中,前兩個部分的值對於一個TCP連接相對固定,路由器緩存中的排隊和處理時間會 隨着整個網絡擁塞程度的變化而變化。 所以RTT的變化在一定程度上反應網絡的擁塞程 度
    在TCP的選項 內容可以插入時間戳。

流量控制

  • 雙方在通信的時候,發送方的速率與接收方的速率是不一定相等,如果發送方的發送速率太快,會導致接收方處理不過來,這時候接收方只能把處理不過來的數據存在緩存區裏(失序的數據包也會被存放在緩存區裏)。
  • 如果緩存區滿了發送方還在瘋狂着發送數據,接收方只能把收到的數據包丟掉,大量的丟包會極大着浪費網絡資源,因此,我們需要控制發送方的發送速率,讓接收方與發送方處於一種動態平衡纔好。
  • 對發送方發送速率的控制,稱之爲流量控制。

如何控制?

  • 接收方每次收到數據包,可以在發送確定報文的時候,同時告訴發送方自己的緩存區還剩
    餘多少是空閒的,我們也把緩存區的剩餘大小稱之爲接收窗口大小,用變量win來表示接收窗口的大小。

  • 發送方收到之後,便會調整自己的發送速率,也就是調整自己發送窗口的大小,當發送方收到接收窗口的大小爲0時,發送方就會停止發送數據,防止出現大量丟包情況的發生。

發送方何時再繼續發送數據?

當發送方停止發送數據後,該怎樣才能知道自己可以繼續發送數據?

  1. 當接收方處理好數據,接受窗口 win > 0 時,接收方發個通知報文去通知發送方,告訴他可以繼續發送數據了。當發送方收到窗口大於0的報文時,就繼續發送數據。
  2. 當發送方收到接受窗口 win = 0 時,這時發送方停止發送報文,並且同時開啓一個定時器,每隔一段時間就發個測試報文去詢問接收方,打聽是否可以繼續發送數據了,如果可以,接收方就告訴他此時接受窗口的大小;如果接受窗口大小還是爲0,則發送方再次刷新啓動定時器。

小結

  1. 通信的雙方都擁有兩個滑動窗口,一個用於接受數據,稱之爲接收窗口;一個用於發送數據,稱之爲擁塞窗口(即發送窗口)。指出接受窗口大小的通知我們稱之爲窗口通告。
  2. 接收窗口的大小固定嗎?接受窗口的大小是根據某種算法動態調整的。
  3. 接收窗口越大越好嗎?當接收窗口達到某個值的時候,再增大的話也不怎麼會減少丟包率的了,而且還會更加消耗內存。所以接收窗口的大小必須根據網絡環境以及發送發的的擁塞窗口來動態調整.
  4. 發送窗口和接受窗口相等嗎?接收方在發送確認報文的時候,會告訴發送發自己的接收窗口大小,而發送方的發送窗口會據此來設置自己的發送窗口,但這並不意味着他們就會相等。首先接收方把確認報文發出去的那一刻,就已經在一邊處理堆在自己緩存區的數據了,所以一般情況下接收窗口 >= 發送窗口。

2.5 阻塞控制

阻塞控制和流量控制雖然採取的動作很相似,但擁塞控制與網絡的擁堵情況相關聯,而流量控制與接收方的緩存狀態相關聯。

2.6 UDP併發編程

源碼路徑:
https://github.com/wangbojing/udp_server_concurrent

UDP如何可靠,KCP協議在哪些方面有優勢

以10%-20%帶寬浪費的代價換取了比 TCP快30%-40%的傳輸速度。
RTO翻倍vs不翻倍:
TCP超時計算是RTOx2,這樣連續丟三次包就變成RTOx8了,十分恐怖,而KCP啓動快速模式後不x2,只是x1.5(實驗證明1.5這個值相對比較好),提高了傳輸速度。
以RTO=100ms爲例:

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

快速重傳(跳過多少個包馬上重傳)(如果使用了快速重傳,可以不考慮RTO)):
發送端發送了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爲了充分利用帶寬,延遲發送ACK(NODELAY都沒用),這樣超時計算會算出較大RTT時間,延長了丟包時的判斷過程。KCP的ACK是否延遲發送可以調節。

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

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

KCP精講-名詞說明

  • kcp官方:https://github.com/skywind3000/kcp
  • 名詞說明
    用戶數據:應用層發送的數據,如一張圖片2Kb的數據
    MTU:最大傳輸單元。即每次發送的最大數據,1500 實際使用1400
    RTO:Retransmission TimeOut,重傳超時時間。
    cwnd: congestion window,擁塞窗口,表示發送方可發送多少個KCP數據包。與接
    收方窗口有關,與網絡狀況(擁塞控制)有關,與發送窗口大小有關。
    rwnd: receiver window,接收方窗口大小,表示接收方還可接收多少個KCP數據包
    snd_queue: 待發送KCP數據包隊列
    snd_nxt:下一個即將發送的kcp數據包序列號
    snd_una:下一個待確認的序列號,即是之前的包接收端都已經收到。

kcp使用方式

  1. 創建 KCP對象:ikcpcb *kcp = ikcp_create(conv, user);
  2. 設置發送回調函數(如UDP的send函數):kcp->output = udp_output;
  3. 真正發送數據需要調用sendto
  4. 循環調用 update:ikcp_update(kcp, millisec);
  5. 輸入一個應用層數據包(如UDP收到的數據包):kcp_input(kcp,received_udp_packet,received_udp_size);
  6. 我們要使用recvfrom接收,然後扔到kcp裏面做解析
  7. 發送數據:ikcp_send(kcp1, buffer, 8); 用戶層接口
  8. 接收數據:hr = ikcp_recv(kcp2, buffer, 10)

kcp源碼流程圖

kcp配置模式

  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, 10, 0, 0);
    普通模式: ikcp_nodelay(kcp, 0, 10, 0, 1);關閉流控等
    極速模式: ikcp_nodelay(kcp, 2, 10, 2, 1),並且修改kcp1->rx_minrto = 10;kcp1->fastresend = 1;

  2. 最大窗口:int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
    該調用將會設置協議的最大發送窗口和最大接收窗口大小,默認爲32,單位爲包。

  3. 最大傳輸單元:int ikcp_setmtu(ikcpcb *kcp, int mtu);
    kcp協議並不負責探測 MTU,默認 mtu是1400字節

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

kcp協議頭

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

kcp發送數據過程

接收窗口

snd_wnd:固定大小,默認32
rmt_wnd:遠端接收窗口大小,默認32
cwnd:滑動窗口,可變,越小一次能發送的數據越小
接收窗口的控制是:recv_queue的接收能力,比如默認接收端口爲32,如果recv_queue接收了32個包後則接收窗口爲0,然後用戶讀走了32個包,則接收窗口變爲32。
IKCP_CMD_PUSH 命令

kcp確認包處理流程

kcp快速確認

ikcp_input邏輯

應答列表acklist

  • 收到包後將序號,時間戳存儲到應答列表。
    在ikcp_input函數調用ikcp_ack_push存儲應答包ikcp_ack_push(kcp, sn, ts); // 對該報文的確認 ACK 報文放入 ACK 列表中
  • 發送應答包
    在ikcp_flush函數發送應答包
  • 應答包解析
    在ikcp_input函數進行解析,判斷IKCP_CMD_ACK

流量控制和擁塞控制

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

探測對方接收窗口

ikcp_flush 發送探測窗口IKCP_CMD_WASK

ikcp_input函數
cmd == IKCP_CMD_WASK,標記kcp->probe |= IKCP_ASK_TELL;
ikcp_flush 迴應探測IKCP_CMD_WINS

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章