網游中的網絡編程系列1:UDP vs. TCP


原文:UDP vs. TCP,作者是Glenn Fiedler,專注於游戲網絡編程相關工作多年。

目錄

  1. 網游中的網絡編程系列1:UDP vs. TCP
  2. 網游中的網絡編程2:發送和接收數據包
  3. 網游中的網絡編程3:在UDP上建立虛擬連接
  4. TODO

說在最前面的話

翻譯這篇文章的初衷:我在工作中根本接觸不到網絡游戲編程,但是我不想把自己定義為‘網站開發工程師’,正像我師父告訴我的:“別說開發網站,太low!要說開發web應用”。那么,網絡游戲開發web方面的知識真的應該了解下。鍛煉自己英文的同時,可以分享些東西給“小伙們”,哇咔咔~。

這是一個系列的文章,前面的東西一定要弄懂,后面就會輕松的搞明白(我后面的還沒看,但是要先給自己打打氣😋)。

注:括號中的內容都是我加上去的,為了有助於理解。

介紹

你一定聽說過sokcet(初探socket),它分為兩種常用類型:TCP和UDP。當要寫一個網絡游戲,我們首先要選擇使用哪種類型的socket。是用TCP、UDP還是兩者都用?

選擇哪種類型,完全取決於你要寫的游戲的類型。后面的文章,我都將假設你要寫一個‘動作’網游。就像:光環系列,戰地1942,雷神之錘,這些游戲。

我們將非常仔細的分析這兩種socket類型的優劣,並且深入到底層,弄清楚互聯網是如何工作的什么。當我們弄清楚這些信息后,就很容易做出正確的選擇了。

TCP/IP

TCP代表“傳輸控制協議”,IP代表:“互聯網協議”,你在互聯網上做任何事情,都是建立在這兩者的基礎上,比如:瀏覽網頁、收發郵件等等。

TCP

如果你曾經用過TCP socket,你肯定知道它是可靠連接的協議,面向連接的傳輸協議。簡單的說:兩台機器先建立起連接,然后兩台機器相互發送數據,就像你在一台計算機上寫文件,在另外一個台讀文件一樣。(我是這么理解的:TCP socket就像建立起連接的計算機,之間共享的一個'文件'對象,兩者通過讀寫這個'文件'實現數據的傳輸)

這個連接是可靠的、有序的,代表着:發送的所有的數據,保證到達傳輸的另一端的時候。另一端得到的數據,和發送數據一摸一樣(可靠,有序。例如:A發送數據‘abc’,通過TCP socket傳輸數據到B,B得到數據一定是:‘abc’。而不是‘bca’或者‘xueweihan’之類的鬼!😋)。傳輸的數據是‘數據流’的形式(數據流:用於操作數據集合的最小的有序單位,與操作本地文件中的stream一樣。所以TCP socket和文件對象很像),也就是說:TCP把你的數據拆分后,包裝成數據包,然后通過網絡發送出去。

注意:就像讀寫文件那樣,這樣比較好理解。

IP

“IP”協議是在TCP協議的下面(這個牽扯到七層互聯網協議棧,我就簡單的貼個圖不做詳細的介紹)

“IP”協議是沒有連接的概念,它做的只是把上一層(傳輸層)的數據包從一個計算傳遞到下一個計算機。你可以理解成:這個過程就像一堆人手遞手傳遞紙條一樣,傳遞了很多次,最終到達紙條上標記的xxx手里(紙條上寫着‘xxx親啟,偷看者3cm’😄)。

在傳遞的過程中,不保證這個紙條(信件)能能夠准確的送到收信人的手上。發信人發送信件,但是永遠不知道信件是否可以准確到達收件人的手上,除非收件人回信告訴他(發信人):“兄弟我收到信了!”(IP層只是用於傳遞信息,並不做信息的校驗等其它操作)

當然,傳遞信息的這個過程還是還是很復雜的。因為,不知道具體的傳遞次序,也就是說,因為不知道最優的傳遞路線(能夠讓數據包快速的到達目的地的最優路徑)所以,有些時候“IP”協議就傳遞多份一樣的數據,這些數據通過不同的路線到達目的地,從而發現最優的傳遞路線。

這就是互聯網設計中的:自動優化和自動修復,解決了連接的問題。這真的是一個很酷的設計,如果你想知道更多的底層實現,可以閱讀關於TCP/IP的書。(推薦上野宣的圖解系列)

UDP

如果我們想要直接發送和接受數據包,那么就要使用另一種socket。

我們叫它UDP。UDP代表“用戶數據包協議”,它是另外一種建立在IP協議之上的協議,就像TCP一樣,但是沒有TCP那么多功能(例如:建立連接,信息的校驗,數據流的拆分合並等)

使用UDP我們能夠向目標IP和端口(例如80),發送數據包。數據包會達到目標計算機或者丟失。

收件人(目標計算機),我們只需要監聽具體的端口(例如:80),當從任意一台計算機(注意:UDP是不建立連接的)接受到數據包后,我們會得知發送數據包的計算機地址(IP地址)和端口、數據包的大小、內容。

UDP是不可靠協議。現實使用的過程中,發送的大多數的數據包都會被接收到,但是通常會丟失1-5%,偶爾,有的時候還可能啥都接收不到(數據包全部丟失一個都沒接收到,傳遞數據的計算機之間的計算機的數量越多,出錯的概率越大)。

UDP協議中的數據包也是沒有順序的。比如:你發送5個包,順序是1,2,3,4,5。但是,即接收到的順序可能是3,1,4,2,5。現實使用的過程中,大多時候,接收到的數據的順序是正確的,但是並不是每次都是這樣。

最后,盡管UDP並沒有比“IP”協議高級多少,而且不可靠。但是你發送的數據,要么全部到達,要么全部丟失。比如:你發送一個大小為256 byte的數據包給另外一台計算機,這台計算機不會只接收到100 byte的數據包,它只可能接收到256 byte的數據包,或者什么都沒接收到。這是UDP唯一可以保證的事情,其它所有的事情都需要你來決定(我的理解,UDP協議只是個簡單的傳輸協議,只保證數據包的完整性,注意是數據包而不是信息。其他的事情需要自己去做,完善這個協議,達到自己使用的需求。)

TCP vs. UDP

我們如何選擇是使用TCP socket還是UDP socket呢?

我們先看看兩者的特征吧:

TCP:

  • 面向連接

  • 可靠、有序

  • 自動把數據拆分成數據包

  • 確保數據的發送一直在控制中(流量控制)

  • 使用簡單,就像讀寫文件一樣

UDP:

  • 沒有連接的概念,你需要自己通過代碼實現(這個我也沒自己實現過,應該還會講)

  • 不可靠,數據包無序,數據包可能無序,重復,或者丟失

  • 你需要手動地把數據拆分成數據包,然后發送數據包

  • 你需要自己做流量控制

  • 如果數據包太多,你需要設計重發和統計機制

通過上面的描述,不難發現:TCP做了所有我們想做的事情,而且使用十分簡單。反觀UDP就十分難用了,我們需要自己編寫設計一切。很顯然,我們只要用TCP就好了!

不,你想的簡單了(原來,是我太年輕了!)

當你開發一個像上面說過的FPS(動作網游)的時候使用TCP協議,會是一個錯誤的決定,這個TCP協議就不好用了!為什么這么說?那么你就需要知道TCP到底做了什么,使得一起看起來十分簡單。(讓我們繼續往下看,這是我最好奇的地方!!!有沒有興奮起來?🤓)

TCP內部的工作原理

TCP和UDP都是建立在“IP”協議上的,但是它倆完全不同。UDP和“IP”協議很像,然而TCP隱藏了數據包的所有的復雜和不可靠的部分,抽象成了類似文件的對象。

那么TCP是如何做到這一點呢?

首先,TCP是一個數據流的協議,所以你只需要把輸入的內容變成數據流,然后TCP協議就會確保數據會到達發送的目的地。因為“IP”協議是通過數據包傳遞信息,TCP是建立在“IP”協議之上,所以TCP必須把用戶輸入的數據流分成數據包的形式。TCP協議會對需要發送的數據進行排隊,然后當有足夠的排除數據的時候,就發送數據包到目標計算機。

當在多人在線的網絡游戲中發送非常小的數據包的時候,這樣做就有一個問題。這個時候會發生什么?如果數據沒有達到緩沖區設定的數值,數據包是不會發送的。這就會出現個問題:因為客戶端的用戶輸入請求后,需要盡快的從服務器得到響應,如果像上面TCP 等待緩沖區滿后才發送的話,就會出現延時,那么客戶端的用戶體驗就會非常差!網絡游戲幾乎不能出現延時,我們希望看到的是“實時”和流暢。

TCP有一個選項可以修復,上面說的那種等待緩沖區滿才發送的情況,就是TCP_NODELAY。 這個選項使得TCP socket不需要等待緩沖區滿才發送,而是輸入數據后就立即發送。

然而,即使你已經設置了TCP_NODELAY選項,在多人網游中還是會有一系列的問題。

這一切的源頭都由於TCP處理丟包和亂序包的方式。使得你產生有序和可靠的“錯覺”。

TCP如何保證數據的可靠性

本質上TCP做的事情,分解數據流,成為數據包,使用在不可靠的“IP”協議,發送這些數據包。然后使得數據包到達目標計算機,然后重組成數據流。

但是,如何處理當丟包?如何處理重復的數據包和亂序數據包?

這里不會介紹TCP處理這些事情的細節,因為這些都是非常復雜的(想弄清楚的同學可以看我上面推薦的書單),大體上:TCP發送一個數據包,等待一段時間,直到檢測到數據包丟失了,因為沒有接收到它的ACK(一種傳輸類控制符號,用於確認接收無誤),接下來就重新發送丟失的數據包到目標計算機。重復的數據包將被丟棄在接收端,亂序的數據包將被重新排序。所以保證了數據包的可靠性和有序性。

如果我們用TCP實現數據的實時傳輸,就會出現一個問題:TCP無論什么情況,只要數據包出錯,就必須等待數據包的重發。也就是說,即使最新的數據已經到達,但還是不能訪問這些數據包,新到的數據會被放在一個隊列中,需要等待丟失的包重新發過來之后,所有數據沒有丟失才可以訪問。需要等待多長時間才能重新發送數據包?舉個例子:如果的延時是125ms,那么需要最好的情況下重發數據包需要250ms,但是如果遇到糟糕的情況,將會等待500ms以上,比如:網絡堵塞等情況。那就沒救了。。。

為什么TCP不應該用於對網絡延時要求極高的條件下

如果FPS(第一人稱射擊)這類的網絡游戲使用TCP就出現問題,但是web瀏覽器、郵箱、大多數應用就沒問題,因為多人網絡游戲有實時性的要求。比如:玩家輸入角色的位置,重要的不是前一秒發生了什么,而是最新的情況!TCP並沒有考慮這類需求,它並不是為這種需求而設計的。

這里舉一個簡單的多人網游的例子,比如射擊的游戲。對網絡的要求很簡單。玩家通過客戶端發送給服務器的每個場景(用鼠標和鍵盤輸入的行走的位置),服務器處理每個用戶發送過來的所有場景,處理完再返回給客戶端,客戶端解析響應,渲染最新的場景展示給玩家。

在上面說的哪個多人游戲的例子中,如果出現一個數據包丟失,所有事情都需要停下來等待這個數據包重發。客戶端會出現等待接收數據,所以玩家操作的任務就會出現站着不動的情況(卡!卡!卡!😡💢),不能射擊也不能移動。當重發的數據包到達后,你接收到這個過時的數據包,然而玩家並不關心過期的數據(激戰中,卡了1秒,等能動了,都已經死了💀)

不幸的是,沒有辦法修復TCP的這個問題,這是它本質的東西,沒辦法修復。這就是TCP如何做到讓不可靠,無序的數據包,看起來像有序,可靠的數據流。

我並不需要可靠,有序的數據流,我們希望的是客戶端和服務端之間的延時越低越好,不需要等待重發丟失的包。

所以,這就是為什么在對數據的實時性要求的下,我們不用TCP。

那為什么不UDP和TCP一起用呢?

像玩家輸入實時游戲數據和狀態的變更,只和最新的數據有關(這些數據強調實時性)。但是另外的一些數據,例如,從一台計算機發送給另外一個台計算機的一些列指令(交易請求,聊天?),可靠、有序的傳輸還是非常重要的!

那么,用戶輸入和狀態用UDP,TCP用於可靠、有序的數據傳輸,看起來是個不錯的點子。但是,問題在於TCP和UDP都是建立“IP”協議之上,所以協議之間都是發送數據包,從而相互通信。協議之間的互相影響是相當復雜的,涉及到TCP性能、可靠性和流量控制。簡而言之,TCP會導致UDP丟包,請參考這篇論文

此外,UDP和TCP混合使用是非常復雜的,而且實現起來是非常痛苦的。(這段我就不翻譯了,總而言之:不要混用UDP和TCP,容易失去對傳輸數據的控制)

總結

我的建議並不是就一定要使用UDP,但是UDP協議應該用於游戲。請不要混合使用TCP和UDP,你應該學習TCP中一些地方是如何實現的技巧,然后可以把這些技巧用在UDP上,從而實現適合你的需求的協議(借鑒TCP中的實現,在UDP上,完善功能,從而達到你的需求)。

這個系列,接下來會講到:如何在UDP上創建一個虛擬的連接(因為UDP本身,是沒有連接的概念的)、如何使得UDP實現可靠性,流量控制,非阻塞。

參考


免責聲明!

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



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