KCP协议:从TCP到UDP家族QUIC/KCP/ENET
行文前先安利下《再深談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協議?在網絡中狂奔:KCP協議 - 知乎
可靠UDP,KCP協議快在哪??https://wetest.qq.com/lab/view/391.html
KCP 協議與源碼分析(一)?https://github.com/skywind3000/kcp
網絡編程懶人入門(五):快速理解為什么說UDP有時比TCP更有優勢?網絡編程懶人入門(五):快速理解為什么說UDP有時比TCP更有優勢-網絡編程/專項技術區 - 即時通訊開發者社區!
轉載本站文章《KCP協議:從TCP到UDP家族QUIC/KCP/ENET》,
請注明出處:KCP協議:從TCP到UDP家族QUIC/KCP/ENET - Network - 周陸軍的個人網站
總結
以上是生活随笔為你收集整理的KCP协议:从TCP到UDP家族QUIC/KCP/ENET的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 河道、地下水位监测方案
- 下一篇: 海贼王热血航线服务器维护,航海王热血航线