咱們日常玩的不少網絡遊戲,好比英雄聯盟/王者榮耀/PUBG等,你感受到卡頓每每不是由於你的網速問題,而是由於網絡延時致使的,好比說LOL美服的遊戲服務器在美國,而你在中國的華中地區玩着美服LOL,那麼你的延遲可能會在300ms左右,由於網絡請求從美國到中國華中地區須要通過不少的路由,這裏面會消耗掉不少時間,若是發生了丟包,那麼重發須要的延遲更是會加倍增加,而延遲每每在150ms以上時每每就會影響到你的遊戲體驗了。git
市面上會有一些遊戲加速器,它們會在國外安置服務器,搭建一條線路,來保證你的請求可以迅速的被處理,來下降遊戲的延遲。github
如今不少的遊戲以及直播等低延遲需求的應用,通常都不會再使用原生的TCP或者UDP來進行傳輸,而是在二者的基礎上進行擴展修改,取其優異,好比TCP的傳輸可靠,UDP的傳輸速度。算法
仔細想一想之前在計算機網絡課程中學習TCP/UDP時,就對TCP的所謂可靠傳輸感受很怪異,真的是可靠到太過慎重了,說到慎重就不得不提這個月的一部新番《這個勇者明明超強卻過度慎重 》。緩存
昨天看了第一話,吹爆!服務器
說回TCP,當時以爲它的超時重傳RTO時間每次都會翻倍,若是一個包屢次超時,那下次重發這個包不是須要好久,延遲這不就上來了? 還有它的重傳,丟了一個包就須要重傳以後全部的包,過度的慎重,雖說能夠保證可靠性,可是這對於咱們毫秒級即時通信之類的應用確實不太友好。網絡
在此前提下,有了不少基於TCP或是UDP的改良,專門針對網絡遊戲以及音視頻通話中的延遲,本篇要說的就是KCP協議。tcp
KCP是一個快速可靠協議,能以比 TCP浪費10%-20%的帶寬的代價,換取平均延遲下降 30%-40%,且最大延遲下降三倍的傳輸效果。函數
純算法實現,並不負責底層協議(如UDP)的收發,須要使用者本身定義下層數據包的發送方式,以 callback的方式提供給 KCP。學習
連時鐘都須要外部傳遞進來,內部不會有任何一次系統調用。spa
有一種叫KCPtun的實現,能夠把咱們的TCP請求轉化成KCP+UDP在公網上傳輸。
TCP是爲流量設計的(每秒內能夠傳輸多少KB的數據),講究的是充分利用帶寬。而 KCP是爲流速設計的(單個數據包從一端發送到一端須要多少時間),以10%-20%帶寬浪費的代價換取了比 TCP快30%-40%的傳輸速度。
TCP信道是一條流速很慢,但每秒流量很大的大運河,而KCP是水流湍急的小激流。
KCP有正常模式和快速模式兩種,經過如下策略達到提升流速的結果:
TCP超時計算是RTOx2,這樣連續丟三次包就變成RTOx8了,十分恐怖,而KCP啓動快速模式後不x2,只是x1.5(實驗證實1.5這個值相對比較好),提升了傳輸速度。
TCP丟包時會所有重傳從丟的那個包開始之後的數據,KCP是選擇性重傳,只重傳真正丟失的數據包。
發送端發送了1,2,3,4,5幾個包,而後收到遠端的ACK: 1, 3, 4, 5,當收到ACK3時,KCP知道2被跳過1次,收到ACK4時,知道2被跳過了2次,此時能夠認爲2號丟失,不用等超時,直接重傳2號包,大大改善了丟包時的傳輸速度。
TCP爲了充分利用帶寬,延遲發送ACK(NODELAY都沒用),這樣超時計算會算出較大 RTT時間,延長了丟包時的判斷過程。KCP的ACK是否延遲發送能夠調節。
ARQ模型響應有兩種,UNA(此編號前全部包已收到,如TCP)和ACK(該編號包已收到),光用UNA將致使所有重傳,光用ACK則丟失成本過高,以往協議都是二選其一,而 KCP協議中,除去單獨的 ACK包外,全部包都有UNA信息。
KCP正常模式同TCP同樣使用公平退讓法則,即發送窗口大小由:發送緩存大小、接收端剩餘接收緩存大小、丟包退讓及慢啓動這四要素決定。但傳送及時性要求很高的小數據時,可選擇經過配置跳事後兩步,僅用前兩項來控制發送頻率。以犧牲部分公平性及帶寬利用率之代價,換取了開着BT都能流暢傳輸的效果。
若是網絡永遠不卡,那 KCP/TCP 表現相似,可是網絡自己就是不可靠的,丟包和抖動沒法避免(不然還要各類可靠協議幹嗎)。在內網這種幾乎理想的環境裏直接比較,你們都差很少,可是放到公網上,放到3G/4G網絡狀況下,或者使用內網丟包模擬,差距就很明顯了。公網在高峯期有平均接近10%的丟包,wifi/3g/4g下更糟糕,這些都會讓傳輸變卡。
可能你玩的不少遊戲,或者說使用的加速器,都是利用了KCP來下降延遲。
下面是KCP在GitHub上的地址,只須要將其中ikcp.c,ikcp.h兩個文件導入你的協議棧中就可使用了。
https://github.com/skywind3000/kcp
建立 KCP對象:
// 初始化 kcp對象,conv爲一個表示會話編號的整數,和tcp的 conv同樣,通訊雙 // 方需保證 conv相同,相互的數據包纔可以被承認,user是一個給回調函數的指針 ikcpcb *kcp = ikcp_create(conv, user);
設置回調函數:
// KCP的下層協議輸出函數,KCP須要發送數據時會調用它 // buf/len 表示緩存和長度 // user指針爲 kcp對象建立時傳入的值,用於區別多個 KCP對象 int udp_output(const char *buf, int len, ikcpcb *kcp, void *user) { .... } // 設置回調函數 kcp->output = udp_output;
循環調用 update:
// 以必定頻率調用 ikcp_update來更新 kcp狀態,而且傳入當前時鐘(毫秒單位) // 如 10ms調用一次,或用 ikcp_check肯定下次調用 update的時間沒必要每次調用 ikcp_update(kcp, millisec);
輸入一個下層數據包:
// 收到一個下層數據包(好比UDP包)時須要調用: ikcp_input(kcp, received_udp_packet, received_udp_size);
處理了下層協議的輸出/輸入後 KCP協議就能夠正常工做了,使用 ikcp_send 來向 遠端發送數據。而另外一端使用 ikcp_recv(kcp, ptr, size)來接收數據。