IP協議的抽象簡單概念與IP協議底層傳輸的複雜真實實現形成鮮明對比。
這裏不需要任何連接的概念,數據包從一個電腦傳輸到另外一個電腦,就像你在上課的教室裏面傳紙條一樣。你只要給出一個最終的目的地,包自然會傳到那個終端,當然中間還會經過好多層的路由遞送。
你完全不能確定自己的包最終會不會到達目的地,只能希望它到達。如果你想知道包最終是否到達,那你就必須讓接收者收到以後做出回覆。
事實上,整個過程可能還要更復雜一些,由於沒有一臺機器能夠預知包可以最快到達的完整路由途徑,所以有時候IP協議會將包複製多份發出,這些包會走不同的路由,因此他們一般也會在不同的時刻到達。
你必須瞭解因特網路由問題的設計原則是自主組織路徑,自主修復路徑。所以當你思考問題的底層是如何實現時那是相當帶勁的,當然你可以在相關的教科書上找到你想要的內容或者wiki。
UDP
我們已經有一種可以像寫文件一樣穩定傳輸數據的方法了,如果我們還想要一種可以自由的收發包的方法,我們可以怎麼做呢?
這裏我們可以用到UDP。UDP解釋爲“user datagram protocol”(用戶自定義數據協議)。它和TCP類似也是建立在IP協議上的,不過他相比起TCP來在IP協議上只做了薄薄的一層協議。
我們可以使用UDP協議直接對指定的IP和端口發包,比如 1.0.0.127:21(本機的FTP端口)。這個包會從發送者自己路由到接收者手中,當然也有可能半路丟失掉。
在接收端,我們只要偵聽相關端口就可以了,當有包從任何電腦發來的時候(這裏沒有連接的概念),我們在得到包的數據的同時,也得到了包的發送者的IP和端口數據,發送包的大小。
UDP是不穩定可靠的傳輸協議,事實上大部分的包是會被送達的,但是你一般會有1%到5%的丟包率。甚至你會發現有段時間,你連一個包都收不到。路由路徑上的那些機器出了點啥問題,誰知道呢?
有時候收包的順序和發包的順序也是不同的,可能你發包的時候是1,2,3,4,5的順序發。收到的包就變成1,3,4,5,2的順序了。當然大部分時候這個順序是不會亂的。但是這個誰都不敢打保票。
UDP只能保證你一件事,那就是包的完整性。你如果發一個256bytes的包,那麼對方收到的肯定也是一個256bytes的包。他不會只收到半個包之類的。當然這其實也是UDP唯一能保證你的事情。其他事情都要靠你自己去訂製。 by rellikt
TCP vs UDP
我們現在要做一個選擇了。開發遊戲到底是用UDP好呢?還是用TCP好呢?
首先我們連列一下他們的有缺點:
TCP:
1. 基於連接的協議。
2. 可靠性和數據包的序列性是有保證的。
3. 自動爲你的數據封包。
4. 確保包的流量不會超出你的網絡鏈接的負載上限。
5. 簡單易用,你只需要像寫文件一樣去讀寫就萬事大吉了。
UDP:
1. 沒有連接的概念,如果你想要,自己去實現去。
2. 沒有關於可靠性和包序列性的保證,包可能會丟失,重複,亂序。
3. 你必須自己去封包。
4. 你必須自己確保自己的數據包不會流量過大從而導致超過鏈接負載上限。
5. 你必須自己處理包的丟失,重複,亂序的情況,如果你不想他們對你的程序造成麻煩,必須要自己實現代碼來做出應對。
這樣一比,我們顯然應該用那個TCP協議了。它完成了所有我們想要的特性,實在是太完美了,不是嗎?by rellikt
TCP的真實工作情況
TCP和UDP都是基於IP協議的,但是他們的本質確實截然不同的。UDP和它底層的IP協議類似,TCP卻將很多東西進行了抽象,它使你在使用它的時候感覺就像讀寫一個文件一樣,事實上他對你隱藏了很多複雜功能的實現細節。
它到底是如何實現這些細節的呢?
首先,TCP是流性質的協議,你將數據一個比特一個比特的寫入流,然後TCP來確保他們會最終到底目的地。我們必須明確:TCP協議是基於IP協議的,IP協議是基於數據包的。這裏TCP其實是把我們的數據流在底層進行了打包。事實上有一些TCP協議中的代碼就是把我們的數據流進行排隊,然後依次寫入包中,當包寫入了足夠多的數據以後,它就會把包發走。
這裏就會導致一些問題,因爲你不能控制底層的打包和傳輸,所以當很小的用戶輸入數據寫入數據流的時候,TCP可能會湊滿一定量的數據(比如100bytes),然後再打包發送。這樣的話,在實時性上面就會很差,因爲也許你會需要這些包越快到達越好。這些小延遲也許就會大大的影響你的遊戲性。
TCP中其實有一個TCP_NODELAY的選項,使用這個選項以後,你的數據就會跳過TCP的隊列打包過程,直接發送。
但是即使你使用的這個技術,你在多人網絡遊戲中還是會遇到不小的問題。
這些問題源自於TCP實現可靠傳輸的機制。by rellikt
TCP是如何實現可靠傳輸的
基本上來說,TCP將數據流中的數據做成封包,然後將他們通過不可靠的IP協議發送,然後在接收端重組這些包成爲數據流。
但是當一個包丟失的時候TCP會做些什麼呢?當包重複和亂序的時候TCP又做了些什麼呢?
這裏我不想做太深入的介紹,有興趣的讀者可以在wiki上找到他們需要的細節。大致來說,TCP發現丟包的時候,會要求發送者重發,重複的包會被丟棄,亂序包會被排序,事實上他就是這麼保證傳輸的可靠性的。
這裏的丟包處理對遊戲來說就很糟糕了。TCP中,如果你發現丟包了,必須等待發送者進行重發,在重發的包到來以前,即使有新包來,你也只能讓他們在隊列裏等着,不能讀取,這個等待的時間大概是ping值的1.5倍,如果重發的包再次丟失的話那就是3倍的時間。假設你的ping值是125ms,丟包一次就會延遲200ms左右,如果連續丟包就是400ms,這樣的情況在大多數即時類遊戲中是不能忍受的。
爲何你不能選用TCP作爲遊戲開發的網絡協議
通過上面的論述,其實已經很明白了。在注重即時性的遊戲中,對於延遲的要求是很苛刻的,我們可以丟包,但是如果我們收到了比丟掉的包更新的包的話,我們完全可以不管丟掉的包。我們只關注當前數據。
這裏我們來假設一個最簡單的多人遊戲的模型。比如一個FPS遊戲,你在客戶段每次將輸入的數據(比如前進,跳躍,開火)發送到服務器端,然後服務器端將玩家當前的位置和情況發回給客戶端來做顯示。
在這個最簡單的模型中,只要有一個包丟失了,所有的東西都必須停下來等包的重發,任何操作都得停掉,你不能移動也不能射擊。等到這個包到達的時候,你總算能繼續操作了。但是可能你會發現還有一堆等等待重發的包在排隊,於是你只好繼續等,而且可能你收到的這個重發包對遊戲來說已經失去時效性,完全沒意義了。這樣的遊戲你能忍嗎?
不幸的是你對TCP協議的這個行爲完全無能爲力。這是TCP協議的本性,就是它保證了TCP協議的可靠性的。
我們不需要這樣無法訂製的可靠性協議。我們需要自己進行訂製丟包時的處理原則。這也是我們在開發遊戲時,避免使用TCP協議的原因。
是否可以混合使用TCP和UDP協議呢?
上面的結論中,我們可以明確知道,一些類似玩家操作,玩家位置的時效性相關數據,我們必須不能使用TCP協議。但是有些數據確是必須保證可靠性的,那我們是否可以使用TCP協議來做同步呢?
這個想法是很好的,我們可以在玩家操作等即時性很強的數據上使用UDP協議,在玩家AI,數據加載,玩家對話等序列性很強的數據上使用TCP。如果你願意的話,甚至可以爲不同的AI創建不同的TCP連接,以免一個AI的丟包會影響的另外一個AI的即時性。
看上去這是一個不錯的思路。但是這僅僅是看上去而已。由於TCP和UDP都是基於IP協議的,事實上他們在底層會互相干擾。關於干擾的細節我這裏就不詳細論述了,想了解的讀者可以閱讀這篇文章。 by rellikt
結論
我的建議是在遊戲中僅僅使用UDP作爲網絡協議,即使要使用TCP也是自己在UDP的基礎上實現一種類TCP的協議。這也是現代遊戲中流行的網絡架構。