1. 程式人生 > >UDP可靠傳輸那些事

UDP可靠傳輸那些事

  有空來論壇走走,發現討論udp可靠傳輸又熱了起來,有人認為udp高效率,有人認為udp丟包重傳機制容易控制,還有朋友搞極限測試,當然也有人推銷自己的東西,這裡寫一點我個人的看法。

  udp可靠傳輸其實非常非常的簡單,我最開始接觸udp可靠傳輸大約是在2005年,因為那時候開發FtpAnywhere,由於路由的對映和閘道器nat處理方面,認為udp具有天生優勢,因此開始編寫自己的udp可靠傳輸協議,好象那個時候已經有了udt,我也下了原始碼看了下,不過很快就看不下去了,因為它用了定時器,加上跨平臺處理,導致它的程式碼,反正我看著很亂,理不出一個完整的邏輯圖。但是原理和tcp的基本一樣,並沒有什麼特殊的。後來我寫了我自己的第一個udp可靠傳輸類,QTUdp,這個是最簡單的,也就是和tcp一樣,實現2點之間的傳輸,並不是我現在寫的多點之間無中心p2p傳輸,效率很高,但是當時的編碼我還沒有養成現在的習慣,用了大量的DWORD之類的資料定義,包括包的定義,從現在角度看,不及格,不過至少它可以實用,並整合到了FtpAnywhere軟體中,在除錯和執行過程中,慢慢的統計和發現udp包的傳輸中,最影響效能的部分以及其他一些細節,後來的ut 1.x多點傳輸協議就是在QTUdp基礎上開發的,在到現在 ut 2.x , phoenix 2.x,實現了幾個跨越。

  請相信,可靠udp傳輸從來都不是高效率可靠傳輸的代名詞,影響傳輸效率的最重要因素在於,sendto函式,每次只能投遞一個mtu長度的包,頻繁的系統呼叫極大的影響了極限效能,也許你會說,udp預設可以達到64KB,你可以投遞大包,是的,可以投遞,但是由於網路上mtu裝置的限制,大包會被拆成小包,如果你定義一個包大於mtu,那麼當其中任何一個小包發生丟包的時候,會導致整個包需要重傳,這個開銷非常巨大,特別是在Internet上,而採用mtu大小限制內的包進行傳輸,丟失一個,只需要重傳一個,開銷小的多。udp可靠傳輸的自定義校驗是另外一個限制,為了避免偽造的udp包,我們需要在我們自己的可靠udp包中加入自定義的校驗,這個校驗方法也直接影響到效能,最快的是直接套用crc32校驗,由於目前cpu指令集對這個計算進行了優化,因此它的計算速度幾乎是最快的,但是代價是,人家要破解或者偽造你的udp包也很容易,因為演算法是透明的,以前暴出tcp偽造漏洞也是這樣,由於它的包組成是透明公開,唯一有保密性的是序列號,結果有些系統初始序列號存在規律,結果就導致了安全問題。最後,傳送和接收,由於是在應用層進行[無論你是採用api epoll select overlap io,最終的執行都是在應用層],這個傳送-確認過程中,由於應用層不是象tcp/ip協議棧那樣在核心態執行,因此可能有延遲,不過,目前的cpu核心數量和頻率,這個影響在工業應用[internet 等]已經幾乎可以忽略,唯一產生影響是在進行本機極限效能測試中。TCP的效率要高過udp可靠傳輸,因為它的send函式,幾乎每次都可以拷貝幾十KB,當然你可以將緩衝調整的很大,例如幾百KB或者幾MB,但是預設情況下的幾十KB足夠了,想當於幾十次呼叫udp sendto , 而tcp只需要呼叫一次,其次,tcp的包處理是在核心態進行,確認也是核心態,這就足以與應用層的udp可靠傳輸拉開距離,更別說硬體層的優化了. 那麼,你可能會認為,為什麼書上說udp效能好呢?其實這是針對不可靠udp傳輸,並且是內網 大包,例如

char buffs[32*1024];

memset(buffs,0,32*1024);

sendto(s , buffs,32*1024,.... 發射後不管,無論是否丟包

象這樣發包,效率才會超過tcp,不過,主要就是包頭大小的差距.

   使用udp可靠傳輸的目的是為了它的靈活性,在很多協議傳輸中,例如,遠端視訊流傳輸,jrtlib,通常分為關鍵幀和普通幀,關鍵幀的丟失,會導致普通幀失去作用,這個時候就需要使用udp的可靠傳輸+不可靠傳輸,實現方法是首先以可靠模式投遞關鍵幀,在關鍵幀處理完成後,剩餘的時間用非可靠模式投遞普通幀,每隔指定時間重複這一流程. 如果使用tcp,這個效果就不好了,因為網路頻寬就那麼多,而且頻寬變化很大,把所有的關鍵幀和普通幀都進行可靠傳輸,可能導致不流暢,網路阻塞等.

  關於udp的穿透能力,也就是傳說中的打洞,這根本是個偽命題,有人還在那裡搞什麼測試說打通了4種 nat 模型, 你認為可能嗎? 還有人用猜埠的方法進行打洞,我了個去,工業上能這麼用? udp的穿透其實完全取決於路由[閘道器]的配置,國產的家用或者soho針對p2p進行過調整,但是你用cisco 等專業的大型裝置進行打洞看看,為了保護使用者的安全,一般管理員都設定了高安全非透明,通常,就算是內部同一臺電腦,同一個udp埠,傳送給不同的目標ip資料包,路由都會隨機重新分配一個埠,打洞根本不可能成功的. 所以,如果你為了nat處理而使用udp,建議你還是放棄吧,直接使用tcp+upnp就行.

  關於udp可靠傳輸的效能,有人說使用epoll , overlap io 等,效率高於使用select模型, 我可以根據我的這幾年開發的經歷和測試結果告訴你,在校驗模式,投遞模式以及資料處理邏輯相同的情況下,多核心cpu平臺下,本機效率差距不超過1%,而如果使用在Internet上,效率差距無限接近0,現在是cpu過剩的年代,linux  windows的排程下,如果沒有核心態運算,那麼就會從等待的執行緒中挑選一個,你不要以為從核心態切換到使用者態是不需要開銷的,這個開銷同樣很大,頻繁的排程同樣影響效能,雖然不算在你的程式碼中而算在系統開銷中. 當然,如果你需要在linux系統中同時執行apache等web服務,那效率差距會比較明顯,因為程序和執行緒太多,得不到第一時間的排程.

 關於udp可靠傳輸下緩衝大小,說實話,我是第一次見識,最簡單的點對點 udp可靠傳輸,有人開了上百KB的緩衝,緩衝是個好東西,通常,緩衝越大,效率越高,因為一次投遞的包數量多,IO效能就高,但是,這在Internet上是不提倡的,這種實現我不知道是否經過嚴格的網路丟包和負載測試,在有家用路由小頻寬上傳模式下,如果有多人或者多個網路程式使用頻寬,這個延遲引數變動非常頻繁,不必要的重傳率會非常高,最簡單的測試方法,找個銅包鋁網線,使用1 1對應的非標準接頭法,一頭接電腦,一頭接路由,然後與Internet上遠端進行測試,估計這個丟包重傳的概率會高的嚇人.

  udp可靠傳輸比TCP的慢啟動好?我想不一定,還是我上面那個網線做測試,如果採用快速啟動,在丟包嚴重的網路環境下,頻寬浪費太離譜了,我調整我自己的udp可靠傳輸啟動方法好多次,越來越覺得tcp的慢啟動是非常有道理的,雖然恢復的慢,但是,它因為錯誤重傳而產生頻寬的浪費是非常小的. 當然,如果考慮到效能,還是可以適當調整的快點.

 關於udp可靠傳輸的檔案傳輸效率,這是個偽命題,這個極限是硬體本身造成的,首先,正常的udp可靠傳輸效率肯定高於普通硬碟的讀寫盤速度,即使用memory map 技術對檔案執行加速,還是跟不上udp本身的速度,其次,檔案傳輸協議會影響到傳輸效率,最快的模式是什麼? 就是直接傳送檔案從開始到結尾,和流 [FTP資料連線 HTTP]一樣,這是最快的,但是通常,為了避免資料差錯,會對檔案傳輸內容進行分塊傳輸,並加入校驗.這就影響到了傳輸效率. 你說一輛公共汽車是從起點到終點直達快?還是一站站的停過去快?很明顯,第一個假設脫離實際的.

關於udp可靠傳輸和cpu的關係,有嗎?肯定有,但是在普通應用下,基本沒影響,只有在追求極限,例如本機測試,2G+光釺網路等,才會有明顯的影響,可問題是,如果你的伺服器[電腦]接入的是2G+光釺,你這伺服器得什麼硬體配置? 在一般應用下,cpu和記憶體開銷以及錯誤的丟包重傳才是第一位的. 如果一個udp可靠傳輸,100mbps網路下,必須要Intel piii 1Ghz以上,那在這個基礎上開發出來的應用可能比現在的電腦遊戲還離譜了.  如果不是多對多[因為這個邏輯比普通點對點udp可靠傳輸複雜的太多],普通的點對點udp可靠傳輸, 給幾個以前的測試資料, QtUdp ,環境是Pentium M 1.7G[單核心], Ati xpress主機板 768m ddr2 533[單通道] , 本機器模擬傳輸當時測試的資料大約在 78MB/s , MTU=1380 , 緩衝是 17 * MTU.  , CPU開銷大約是75%, 如果使用目前雙核心,估計可以翻倍. 你可能會覺得這個數字太低,但是請注意當時的硬體環境,這個速度已經超過udt好多好多了. 最基本的點對點udp可靠傳輸,標準crc32校驗,以目前的硬體環境, i74核心, DDR3 1600 , 加上我們的一種特殊包處理技術,本機器UDP小包[1400]可靠傳輸的極限大約在270MB/s,如果還要提升,估計只能和ms 的IIS一樣,編寫網路驅動了,但是x86處理器和記憶體速度始終在提升,說不定明天主頻直接翻倍了...

 其實,我轉向udp可靠傳輸的一個非常重要原因,是IPV6取消了傳輸中ip層的資料校驗,這導致tcp層完全負擔起了資料校驗任務,TCP資料是否還象IPV4下那麼可靠,需要加個問號了,雖然IP層本身的差錯率非常低,但是取消掉校驗,直接帶來的風險到底有多大,並沒有經過實踐的評估,靠ipv6實驗網路得出的是理論結果,也許有一天會出現一種突破性的IPV6 TCP偽造技術. 而如果採用udp可靠傳輸,由於udp本身有校驗,加上我們自己設計的校驗和序列號,這個可靠程度完全超過了IPV4 下的TCP,更別提IPV6下的TCP了.

 udp可靠傳輸,其實非常簡單,只要你有tcp/ip的基礎,最簡單的點對點udp可靠傳輸是非常容易編寫的,無非就是封包,校驗,傳送,確認,重新組包,介面可以模仿tcp的幾個函式,這其中的一個難點是,判斷是否需要重發,這需要根據以前包的確認時間來推導本包,如果你不希望那麼複雜,也可以,經驗數字 [不適合網通到電信], 第一次的重傳時間 750 ms , 第二次是 1600 , 以後每次都是 2000 , 雖然不科學,但是用這個延遲數字,可以保證你足夠的傳輸效率[即使存在小概率丟包],也不會產生大量的重傳,當然最好的方法是從之前包的延遲來推導.

 總之,udp可靠傳輸沒那麼複雜和神祕,它非常簡單,而且,它的效率也沒有各位想象中的那麼高,可能會讓各位失望,但是,它很可愛,你可以隨意的塑造你自己的包,實現各種擴充套件,其中的取捨完全在於聰明的你.