天天看點

網遊中的網絡程式設計系列1:UDP vs. TCP

原文:UDP vs. TCP,作者是Glenn Fiedler,專注于遊戲網絡程式設計相關工作多年。

網遊中的網絡程式設計系列1:UDP vs. TCP

網遊中的網絡程式設計2:發送和接收資料包

網遊中的網絡程式設計3:在UDP上建立虛拟連接配接

TODO

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

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

注:括号中的内容都是我加上去的,為了有助于了解。

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

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

我們将非常仔細的分析這兩種socket類型的優劣,并且深入到底層,弄清楚網際網路是如何工作的什麼。當我們弄清楚這些資訊後,就很容易做出正确的選擇了。

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

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

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

注意:就像讀寫檔案那樣,這樣比較好了解。

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

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

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

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

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

如果我們想要直接發送和接受資料包,那麼就要使用另一種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 socket還是UDP socket呢?

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

TCP:

面向連接配接

可靠、有序

自動把資料拆分成資料包

確定資料的發送一直在控制中(流量控制)

使用簡單,就像讀寫檔案一樣

UDP:

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

不可靠,資料包無序,資料包可能無序,重複,或者丢失

你需要手動地把資料拆分成資料包,然後發送資料包

你需要自己做流量控制

如果資料包太多,你需要設計重發和統計機制

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

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

當你開發一個像上面說過的FPS(動作網遊)的時候使用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做的事情,分解資料流,成為資料包,使用在不可靠的“IP”協定,發送這些資料包。然後使得資料包到達目标計算機,然後重組成資料流。

但是,如何處理當丢包?如何處理重複的資料包和亂序資料包?

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

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

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

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

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

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

我并不需要可靠,有序的資料流,我們希望的是用戶端和服務端之間的延時越低越好,不需要等待重發丢失的包。

是以,這就是為什麼在對資料的實時性要求的下,我們不用TCP。

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

那麼,使用者輸入和狀态用UDP,TCP用于可靠、有序的資料傳輸,看起來是個不錯的點子。但是,問題在于TCP和UDP都是建立“IP”協定之上,是以協定之間都是發送資料包,進而互相通信。協定之間的互相影響是相當複雜的,涉及到TCP性能、可靠性和流量控制。簡而言之,TCP會導緻UDP丢包,請參考這篇論文

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

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

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

MBA lib資料流

WiKi TCP/IP協定族

W3School TCP/IP 協定

UDP和TCP的區