kcp-go源碼解析


kcp-go源碼解析

對kcp-go的源碼解析,有錯誤之處,請一定告之。
sheepbao 2017.0612

概念

ARQ:自動重傳請求(Automatic Repeat-reQuest,ARQ)是OSI模型中數據鏈路層的錯誤糾正協議之一.
RTO:Retransmission TimeOut
FEC:Forward Error Correction

kcp簡介

kcp是一個基於udp實現快速、可靠、向前糾錯的的協議,能以比TCP浪費10%-20%的帶寬的代價,換取平均延遲降低30%-40%,且最大延遲降低三倍的傳輸效果。純算法實現,並不負責底層協議(如UDP)的收發。查看官方文檔kcp

kcp-go是用go實現了kcp協議的一個庫,其實kcp類似tcp,協議的實現也很多參考tcp協議的實現,滑動窗口,快速重傳,選擇性重傳,慢啟動等。
kcp和tcp一樣,也分客戶端和監聽端。

kcp協議

layer model

KCP header

KCP Header Format

代碼結構

着重研究兩個文件kcp.gosess.go

kcp淺析

kcp是基於udp實現的,所有udp的實現這里不做介紹,kcp做的事情就是怎么封裝udp的數據和怎么解析udp的數據,再加各種處理機制,為了重傳,擁塞控制,糾錯等。下面介紹kcp客戶端和服務端整體實現的流程,只是大概介紹一下函數流,不做詳細解析,詳細解析看后面數據流的解析。

kcp client整體函數流

和tcp一樣,kcp要連接服務端需要先撥號,但是和tcp有個很大的不同是,即使服務端沒有啟動,客戶端一樣可以撥號成功,因為實際上這里的撥號沒有發送任何信息,而tcp在這里需要三次握手。

客戶端大體的流程如上面所示,先Dial,建立udp連接,將這個連接封裝成一個會話,然后啟動一個go程,接收udp的消息。

kcp server整體函數流

服務端的大體流程如上圖所示,先Listen,啟動udp監聽,接着用一個go程監控udp的數據包,負責將不同session的數據寫入不同的udp連接,然后解析封裝將數據交給上層。

kcp 數據流詳細解析

不管是kcp的客戶端還是服務端,他們都有io行為,就是讀與寫,我們只分析一個就好了,因為它們讀寫的實現是一樣的,這里分析客戶端的讀與寫。

kcp client 發送消息

讀寫都是在sess.go文件中實現的,Write方法:

假設發送一個hello消息,Write方法會先判斷發送窗口是否已滿,滿的話該函數阻塞,不滿則kcp.Send("hello"),而Send函數實現根據mss的值對數據分段,當然這里的發送的hello,長度太短,只分了一個段,並把它們插入發送的隊列里。

接着判斷參數writeDelay,如果參數設置為false,則立馬發送消息,否則需要任務調度后才會觸發發送,發送消息是由flush函數實現的。

flush函數非常的重要,kcp的重要參數都是在調節這個函數的行為,這個函數只有一個參數ackOnly,意思就是只發送ack,如果ackOnly為true的話,該函數只遍歷ack列表,然后發送,就完事了。 如果不是,也會發送真實數據。 在發送數據前先進行windSize探測,如果開啟了擁塞控制nc=0,則每次發送前檢測服務端的winsize,如果服務端的winsize變小了,自身的winsize也要更着變小,來避免擁塞。如果沒有開啟擁塞控制,就按設置的winsize進行數據發送。
接着循環每個段數據,並判斷每個段數據的是否該重發,還有什么時候重發:

  1. 如果這個段數據首次發送,則直接發送數據。
  2. 如果這個段數據的當前時間大於它自身重發的時間,也就是RTO,則重傳消息。
  3. 如果這個段數據的ack丟失累計超過resent次數,則重傳,也就是快速重傳機制。這個resent參數由resend參數決定。
  4. 如果這個段數據的ack有丟失且沒有新的數據段,則觸發ER,ER相關信息ER

最后通過kcp.output發送消息hello,output是個回調函數,函數的實體是sess.go的:

output函數才是真正的將數據寫入內核中,在寫入之前先進行了fec編碼,fec編碼器的實現是用了一個開源庫github.com/klauspost/reedsolomon,編碼以后的hello就不是和原來的hello一樣了,至少多了幾個字節。 fec編碼器有兩個重要的參數reedsolomon.New(dataShards, parityShards, reedsolomon.WithMaxGoroutines(1)),dataShardsparityShards,這兩個參數決定了fec的冗余度,冗余度越大抗丟包性就越強。

kcp的任務調度器

其實這里任務調度器是一個很簡單的實現,用一個全局變量updater來管理session,代碼文件為updater.go。其中最主要的函數

任務調度器實現了一個堆結構,每當有新的連接,session都會插入到這個堆里,接着for循環每隔interval時間,遍歷這個堆,得到entry然后執行entry.s.update()。而entry.s.update()會執行s.kcp.flush(false)來發送數據。

總結

這里簡單介紹了kcp的整體流程,詳細介紹了發送數據的流程,但未介紹kcp接收數據的流程,其實在客戶端發送數據后,服務端是需要返回ack的,而客戶端也需要根據返回的ack來判斷數據段是否需要重傳還是在隊列里清除該數據段。處理返回來的ack是在函數kcp.Input()函數實現的。具體詳細流程下次再介紹。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM