行文前先安利下《再深談TCP/IP三步握手&四步揮手原理及衍生問題—長文解剖IP 》、《再談UDP協議—淺入理解深度記憶》
KCP協議科普
KCP是一個快速可靠協議,能以比 TCP浪費10%-20%的頻寬的代價,換取平均延遲降低 30%-40%,且最大延遲降低三倍的傳輸效果。
純演算法實現,並不負責底層協議(如UDP)的收發,需要使用者自己定義下層資料包的傳送方式,以 callback的方式提供給 KCP。 連時鐘都需要外部傳遞進來,內部不會有任何一次系統呼叫。本文傳輸協議之考慮UDP的情況。
整個KCP協議主要依靠一個迴圈ikcp_update來驅動整個演算法的運轉,所有的資料傳送,接收,狀態變化都依賴於此,所以如果有操作佔用每一次update的週期過長,或者設定內部重新整理的時間間隔過大,都會導致整個演算法的效率降低。在ikcp_update中最終呼叫的是ikcp_flush,這是協議中的一個核心函式,將資料,確認包,以及視窗探測和應答傳送到對端。
KCP使用ikcp_send傳送資料,該函式呼叫ikcp_output傳送資料,實際上最終呼叫事先註冊的傳送回撥傳送資料。KCP通過ikcp_recv將資料接收出來,如果被分片傳送,將在此自動重組,資料將與傳送前保持一致。
KCP為什麼存在?
首先要看TCP與UDP的區別,TCP與UDP都是傳輸層的協議,比較兩者的區別主要應該是說TCP比UDP多了什麼?
-
面向連線:TCP接收方與傳送方維持了一個狀態(建立連線,斷開連線),雙方知道對方還在。
-
可靠的:傳送出去的資料對方一定能夠接收到,而且是按照傳送的順序收到的。
-
流量控制與擁塞控制:TCP靠譜通過滑動視窗確保,傳送的資料接收方來得及收。TCP無私,發生資料包丟失的時候認為整個網路比較堵,自己放慢資料傳送速度。
TCP/UDP/KCP
TCP
-
TCP協議的可靠性讓使用TCP開發更為簡單,同時它的這種設計也導致了慢的特點。
-
TCP是為流量設計的(每秒內可以傳輸多少KB的資料),講究的是充分利用頻寬。
-
TCP為了實現網路通訊的可靠性,使用了複雜的擁塞控制演算法,建立了繁瑣的握手過程以及重傳策略。由於TCP內建在系統協議棧中,極難對其進行改進。
UDP
-
UDP協議簡單,所以它更快。但是,UDP畢竟是不可靠的,應用層收到的資料可能是缺失、亂序的。
-
UDP協議以其簡單、傳輸快的優勢,在越來越多場景下取代了TCP,如網頁瀏覽、流媒體、實時遊戲、物聯網。
隨著網路技術飛速發展,網速已不再是傳輸的瓶頸,CDN服務商Akamai報告從2008年到2015年7年時間,各個國家網路平均速率由1.5Mbps提升為5.1Mbps,網速提升近4倍。網路環境變好,網路傳輸的延遲、穩定性也隨之改善,UDP的丟包率低於5%,如果再使用應用層重傳,能夠完全確保傳輸的可靠性。
KCP
KCP協議就是在保留UDP快的基礎上,提供可靠的傳輸,應用層使用更加簡單——TCP可靠簡單,但是複雜無私,所以速度慢。KCP儘可能保留UDP快的特點下,保證可靠。
-
TCP是為流量設計的(每秒內可以傳輸多少KB的資料),講究的是充分利用頻寬。
-
KCP是為流速設計的(單個資料包從一端傳送到一端需要多少時間),以10%-20%頻寬浪費的代價換取了比 TCP快30%-40%的傳輸速度。
TCP通道是一條流速很慢,但每秒流量很大的大運河,而KCP是水流湍急的小激流。
MOBA類和“吃雞”遊戲多使用幀同步為主要同步演算法,競技性也較高,無論從流暢性,還是從公平性要求來說,對響應延遲的要求都最高,根據業內經驗,當客戶端與伺服器的網路延遲超過150ms時,會開始出現卡頓,當延遲超過250ms時,會對玩家操作造成較大影響,遊戲無法公平進行。類似地,“吃雞”遊戲(如《絕地求生》)玩法對玩家座標、動作的同步要求極高,延遲稍大導致的資料不一致對體驗都會造成較大影響,其實時性要求接近MOBA類遊戲。而對於傳統mmorpg來說,多采用狀態同步演算法,以屬性養成和裝備獲取為關注點,也有一定競技性,出於對遊戲流暢性的要求,對延遲也有一定要求,同步演算法的優化程度不一樣,這一要求也不一樣,一般情況下為保證遊戲正常進行,需要響應延遲保持在300ms以下。相比之下,對於爐石傳說、鬥地主、夢幻西遊等回合制遊戲來說,同時只有一個玩家在操作雙方資料,無資料競爭,且時間粒度較粗,甚至可通過特效掩蓋延遲,因此對網路延遲的要求不高,即便延遲達到500ms~1000ms,遊戲也能正常進行
不同傳輸層協議在可靠性、流量控制等方面都有差別,而這些技術細節會對延遲造成影響。
tcp追求的是完全可靠性和順序性,丟包後會持續重傳直至該包被確認,否則後續包也不會被上層接收,且重傳採用指數避讓策略,決定重傳時間間隔的RTO(retransmission timeout)不可控制,linux核心實現中最低值為200ms,這樣的機制會導致丟包率短暫升高的情況下應用層訊息響應延遲急劇提高,並不適合實時性高、網路環境複雜的遊戲。
基於udp定製傳輸層協議,引入順序性和適當程度或者可調節程度的可靠性,修改流控演算法。適當放棄重傳,如:設定最大重傳次數,即使重傳失敗,也不需要重新建立連線。比較知名的tcp加速開源方案有:quic、enet、kcp、udt。
kcp/quic/enet協議的區別
先安利下《淺談QUIC協議原理與效能分析及部署方案》,
-
quic 是一個完整固化的 http 應用層協議,目前已經更名 http/3,指定使用 udp(雖然本質上並不一定需要 udp)。其主要目的是為了整合TCP協議的可靠性和udp協議的速度和效率,其主要特性包括:避免前序包阻塞、減少資料包、向前糾錯、會話重啟和並行下載等,然而QUIC對標的是TCP+TLS+SPDY,相比其他方案更重,目前國內用於網路遊戲較少
-
kcp 只是一套基於無連線的資料包文之上的連線和擁塞控制協議,對底層【無連線的資料包文】沒有具體的限制,可以基於 udp,也可以基於偽造的 tcp/icmp 等,也可以基於某些特殊環境的非 internet 網路(比如各種現場通訊匯流排)
-
enet: 有ARQ協議。收發不用自己實現,提供連線管理,心跳機制。支援人數固定。自己實現跨平臺。支援可靠無序通道。沒有擁塞控制。執行緒不安全
其實kcp不能和quic對比(quic vs enet),只是講到UDP的時候,順帶搭上QUIC協議,類似的還有WebRTC
為什麼採用UDP,而不是其他的協議呢?比如SCTP天生就具備TCP/UDP所不具備的各種優點(支援多宿主多流分幀可無序抗syn flooding),但是就比如Windows系統,各種路由器、閘道器都不支援,無法鋪開(除非在私有網路或者專用網路中用)。況且,TCP/UDP的各種問題很多都已經通過技術或技巧給解決了。
KCP的配置模式
在網路中,我們認為傳輸是不可靠的,而在很多場景下我們需要的是可靠的資料,所謂的可靠,指的是資料能夠正常收到,且能夠順序收到,於是就有了ARQ協議,TCP之所以可靠就是基於此。
ARQ協議(Automatic Repeat-reQuest),即自動重傳請求,是傳輸層的錯誤糾正協議之一,它通過使用確認和超時兩個機制,在不可靠的網路上實現可靠的資訊傳輸。
ARQ協議有兩種模式:
停等ARQ協議
同步請求響應模式,基於超時重傳保證可靠。
-
A會為每個即將傳送的資料編號,編號的目的是為了標識資料和給資料排序
-
A傳送完資料之後,會給這次傳送的資料設定一個超時計時器
-
B收到資料,將會返回一個確認,該確認也有自己的編號
-
A收到確認,將刪除副本且取消超時計時器,保留副本的原因是傳輸可能出錯
-
B收到錯誤的資料,或者資料在傳輸過程中出錯,總之就是說B沒有收到想要的資料
-
A在超時計時器的設定時間內沒有收到確認,此時重發資料
所以可靠的TCP有32位序列號和32位確認號,TCP和UDP都有16位校驗和。
連續ARQ協議
可以連續傳送多個分組,而不必每發完一個分組就停下來等待對方確認。
是不是想到了HTTP1.1中的管道模式與HTTP1.0停等模式,但這裡有些許區別,HTTP1.1是中伺服器按照順序響應客戶端請求,但連續ARQ協議不會響應每個資料段,而是僅僅響應編號最大的這個資料段,表示之前的資料都收到了,這個叫做UNA模式,而停等ARQ協議可以看作是ACK模式。
現在已經能夠在不可靠的網路中傳輸可靠的資料,但這不意味著可以隨意傳送資料,頻寬是有限的,接收方的負載也是有限的,所以引入了視窗協議,做流量控制。
視窗協議中有兩種:
擁塞視窗
防止過多的資料注入到網路中,這樣可以使網路中的路由器 和鏈路不至於過載。
與擁塞控制相關的有慢啟動、退半避讓、快重傳、快恢復等。
慢啟動是在剛開始傳送資料時讓視窗緩慢擴張,退半避讓是在網路擁堵時視窗大小減半,快重傳是在網路恢復時及時給予響應,與之配合的就是快恢復。
滑動視窗
接收方告知傳送方自己可以接收緩衝區的大小,通常與連續ARQ協議配合使用。
TCP協議中的16位視窗大小就是為視窗協議提供支援的。而UDP協議的目標是盡最大努力交付,不管你收到沒有,所以沒有該欄位。
TCP協議是面向連線的協議,在資料傳輸前通過三次握手建立連線,傳輸完成後通過四次揮手斷開連線,整個過程表示一次完整的資料傳輸,所以需要4位頭長告知哪些是正在傳輸的資料。
UDP協議是無連線的,兩次資料傳輸沒有任何聯絡,所以需要16位長度告知本次傳輸的資料有多少。同時注意,UDP協議每次傳輸的資料量並不是2^16 - 1 - 8 - 20(8表示UDP頭長,20表示IP頭長),而是與MTU有關,即資料鏈路層的最大傳輸單元(Maximum Transmission Unit),值是1500。
TCP協議中的8位標誌位表示不同的功能,例如當SYN = 1時表示建立連線時讓ack = seq + 1而不做任何驗證,當URG = 1時16位緊急指標生效,緊急指標表示正常資料的起始位置,而之前的資料則表示額外的緊要資料,可以被儘快處理。
當清楚TCP和UDP的工作流程,KCP就很容易理解了。
KCP工作模式:
KCP協議預設模式是一個標準的 ARQ,需要通過配置開啟各項加速開關:
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代表關閉。
KCP有正常模式和快速模式兩種,通過以下策略達到提高流速的結果:
-
普通模式/正常模式: ikcp_nodelay(kcp, 0, 40, 0, 0);
-
極速模式/快速模式: ikcp_nodelay(kcp, 1, 10, 2, 1)
最大視窗:
int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
該呼叫將會設定協議的最大傳送視窗和最大接收視窗大小,預設為32. 這個可以理解為 TCP的 SND_BUF 和 RCV_BUF,只不過單位不一樣 SND/RCV_BUF 單位是位元組,這個單位是包。
最大傳輸單元:
純演算法協議並不負責探測 MTU,預設 mtu是1400位元組,可以使用ikcp_setmtu來設定該值。該值將會影響資料包歸併及分片時候的最大傳輸單元。
最小RTO:
TCP超時計算是RTOx2,這樣連續丟三次包就變成RTOx8了,十分恐怖,而KCP啟動快速模式後不x2,只是x1.5(實驗證明1.5這個值相對比較好),提高了傳輸速度
KCP對比TCP配置
RTO翻倍vs不翻倍:
-
TCP超時計算是RTOx2,這樣連續丟三次包就變成RTOx8了,十分恐怖
-
KCP啟動快速模式後不x2,只是x1.5(實驗證明1.5這個值相對比較好),提高了傳輸速度
選擇性重傳 vs 全部重傳:
-
TCP丟包時會全部重傳從丟的那個包開始以後的資料
-
KCP是選擇性重傳,只重傳真正丟失的資料包。(TCP同樣有選擇重傳SACK,但有區別,後續文章再介紹)。
快速重傳:
與TCP相同,都是通過累計確認實現的,傳送端傳送了1,2,3,4,5幾個包,然後收到遠端的ACK:1,3,4,5,當收到ACK = 3時,KCP知道2被跳過1次,收到ACK = 4時,知道2被跳過了2次,此時可以認為2號丟失,不用等超時,直接重傳2號包,大大改善了丟包時的傳輸速度。1位元組cmd = 81時,sn相當於TCP中的seq,cmd = 82 時,sn相當於TCP中的ack。cmd相當於WebSocket協議中的openCode,即操作碼。
延遲ACK vs 非延遲ACK:
TCP在連續ARQ協議中,不會將一連串的每個資料都響應一次,而是延遲傳送ACK,即上文所說的UNA模式,目的是為了充分利用頻寬,但是這樣會計算出較大的RTT時間,延長了丟包時的判斷過程,而KCP的ACK是否延遲傳送可以調節。
-
TCP為了充分利用頻寬,延遲傳送ACK(NODELAY都沒用),這樣超時計算會算出較大 RTT時間,延長了丟包時的判斷過程。
-
KCP的ACK是否延遲傳送可以調節。
UNA vs ACK+UNA:
ARQ模型響應有兩種,UNA(此編號前所有包已收到,如TCP)和ACK(該編號包已收到),光用UNA將導致全部重傳,光用ACK則丟失成本太高,以往協議都是二選其一,而 KCP協議中,除去單獨的 ACK包外,所有包都有UNA資訊。
非退讓流控:
KCP正常模式同TCP一樣使用公平退讓法則,即傳送視窗大小由:傳送快取大小、接收端剩餘接收快取大小、丟包退讓及慢啟動這四要素決定。但傳送及時性要求很高的小資料時,可選擇通過配置跳過後兩步,僅用前兩項來控制傳送頻率。以犧牲部分公平性及頻寬利用率之代價,換取了開著BT都能流暢傳輸的效果
在傳輸及時性要求很高的小資料時,可以通過配置忽略上文所說的視窗協議中的擁塞視窗機制,而僅僅依賴於滑動視窗。2位元組wnd與TCP協議中的16位視窗大小意義相同,值得一提的是,KCP協議的視窗控制還有其它途徑,當cmd = 83時,表示詢問遠端視窗大小,當cmd = 84時,表示告知遠端視窗大小。
4位元組conv表示會話匹配數字,為了在KCP基於UDP實現時,讓無連線的協議知道哪個是哪個,相當於WEB系統HTTP協議中的SessionID。
1位元組frg表示拆資料時的編號,4位元組len表示整個資料的長度,相當於WebSocket協議中的len。
IKCPCB結構
IKCPCB是KCP中最重要的結構,也是在會話開始就建立的物件,代表著這次會話,所以這個結構體體現了一個會話所需要涉及到的所有元件。其中一些引數在IKCPSEG中已經描述,不再多說。
-
conv:標識這個會話;
-
mtu:最大傳輸單元,預設資料為1400,最小為50;
-
mss:最大分片大小,不大於mtu;
-
state:連線狀態(0xFFFFFFFF表示斷開連線);
-
snd_una:第一個未確認的包;
-
snd_nxt:下一個待分配的包的序號;
-
rcv_nxt:待接收訊息序號。為了保證包的順序,接收方會維護一個接收視窗,接收視窗有一個起始序號rcv_nxt(待接收訊息序號)以及尾序號 rcv_nxt + rcv_wnd(接收視窗大小);
-
ssthresh:擁塞視窗閾值,以包為單位(TCP以位元組為單位);
-
rx_rttval:RTT的變化量,代表連線的抖動情況;
-
rx_srtt:smoothed round trip time,平滑後的RTT;
-
rx_rto:由ACK接收延遲計算出來的重傳超時時間;
-
rx_minrto:最小重傳超時時間;
-
snd_wnd:傳送視窗大小;
-
rcv_wnd:接收視窗大小;
-
rmt_wnd:遠端接收視窗大小;
-
cwnd:擁塞視窗大小;
-
probe:探查變數,IKCP_ASK_TELL表示告知遠端視窗大小。IKCP_ASK_SEND表示請求遠端告知視窗大小;
-
interval:內部flush重新整理間隔,對系統迴圈效率有非常重要影響;
-
ts_flush:下次flush重新整理時間戳;
-
xmit:傳送segment的次數,當segment的xmit增加時,xmit增加(第一次或重傳除外);
-
rcv_buf:接收訊息的快取;
-
nrcv_buf:接收快取中訊息數量;
-
snd_buf:傳送訊息的快取;
-
nsnd_buf:傳送快取中訊息數量;
-
rcv_queue:接收訊息的佇列
-
nrcv_que:接收佇列中訊息數量;
-
snd_queue:傳送訊息的佇列;
-
nsnd_que:傳送佇列中訊息數量;
-
nodelay:是否啟動無延遲模式。無延遲模式rtomin將設定為0,擁塞控制不啟動;
-
updated:是否呼叫過update函式的標識;
-
ts_probe:下次探查視窗的時間戳;
-
probe_wait:探查視窗需要等待的時間;
-
dead_link:最大重傳次數,被認為連線中斷;
-
incr:可傳送的最大資料量;
-
acklist:待傳送的ack列表;
-
ackcount:acklist中ack的數量,每個ack在acklist中儲存ts,sn兩個量;
-
ackblock:2的倍數,標識acklist最大可容納的ack數量;
-
user:指標,可以任意放置代表使用者的資料,也可以設定程式中需要傳遞的變數;
-
buffer:儲存訊息位元組流;
-
fastresend:觸發快速重傳的重複ACK個數;
-
nocwnd:取消擁塞控制;
-
stream:是否採用流傳輸模式;
-
logmask:日誌的型別,如IKCP_LOG_IN_DATA,方便除錯;
-
output udp:傳送訊息的回撥函式;
-
writelog:寫日誌的回撥函式。
參考文章:
在網路中狂奔:KCP協議 https://zhuanlan.zhihu.com/p/112442341
可靠UDP,KCP協議快在哪? https://wetest.qq.com/lab/view/391.html
KCP 協議與原始碼分析(一) https://github.com/skywind3000/kcp
網路程式設計懶人入門(五):快速理解為什麼說UDP有時比TCP更有優勢 http://www.52im.net/thread-1277-1-1.html
轉載本站文章《KCP協議:從TCP到UDP家族QUIC/KCP/ENET》,
請註明出處:https://www.zhoulujun.net/html/theory/ComputerScienceTechnology/network/2016_0106_387.html