Unity C# Scoke 如何實現網路通訊
強聯網在我們的遊戲開發中所佔比重越來越大,尤其是開發MMO遊戲時,更需要強聯網來進行實時更新,所以我們就有了強聯網的需要。
首先我們得清楚強聯網的工作原理,說到強聯網,我們就必須說到socket。
socket是對tcp/ip協議的封裝和應用,是面向程式設計師的,給我們提供了操作網路的介面,但是我們也必須基本瞭解其工作原理:
強聯網我們主要使用的是TCP和UDP,首先我們說一下TCP。
一說到TCP,必然會想到三次握手和四次揮手,建立連線和斷開連線的原理雖然在程式設計過程中不會涉及太多,但還是有了解的必要。
三次握手:客戶端和服務端建立連線需要三次握手
第一次:客戶端向服務端傳送報文,向伺服器傳送連線請求;
第二次:服務端向客戶端返回ACK報文,通知客戶端可以連線;
第三次:客戶端收到服務端報文,正式連線服務端。
三次握手完成。
四次揮手:客戶端要與伺服器斷開連線,需要四次揮手
第一次:客戶端向服務端傳送FIN報文,向伺服器傳送中斷連線請求;
第二次:伺服器收到客戶端中斷請求,向伺服器傳送已得知中斷請求,但伺服器還有資源未處理,需要等待;
第三次:伺服器處理完資料後,再次向客戶端傳送報文,告訴客戶端可以斷開連線了;
第四次:客戶端收到服務端斷開連線的確認資訊後,最後傳送資訊看是否真的斷開連線了,如果伺服器沒有一段時間沒有迴應,則說明已經斷開,中斷過程完成;
四次揮手完成。
那麼我們如何使用強聯網呢?
在c#中我們可以通過使用Socket來進行連線:
在服務端的流程:
建立Socket->繫結IP埠->設定排隊連線請求數量->啟動監聽->收發訊息->關閉
在客戶端的流程:
建立Socket->連線對應IP埠->開始收發->關閉
我們會發現,服務端的流程比客戶端長,那是因為客戶端只需要連線一個伺服器就好了,而服務端要接收不同的客戶端。
具體Socket介面的使用在網上都有教程,在這裡不做多述,只需要注意一點就是選擇TCP,UDP使用不同的引數:
serverSocket =new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
這個是TCP的連線,三個引數分別是地址簇(ipv4),傳輸模式(流模式),協議(TCP)
而如果我們要UDP連線,則要如下:
serverSocket =new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);
UDP所需要的傳輸模式是資料報模式,所以我們在改變協議的同時,也要記得改變傳輸型別。
接下來就進入了我們的正題,有一定開發經驗的遊戲開發者一定知道,在網路傳輸資料的過程中會出現分包粘包的現象,我們首先來說一下什麼叫分包、粘包。
分包:傳輸資料不完整,一條資訊被分成多次傳送。
比如我們傳送了一條資訊:“你好”,如果分包現象發生,我們可能只收到了“你”,卻沒有收到“好”,這樣就會導致資料的不完整。
粘包:傳輸的多條資料粘在一起,比如我發了兩句話“你好”和“我是小李”,我們可能會收到“你好我是小李”,也可能收到“你好我是”“你好我”“你好我是小”,後幾種情況是分包粘包同時發生,我們肯定不期望這種現象發生,所以我們就有必要對我們傳送的資料進行編輯。
解決方案: 我們每傳送的一條資料就是一個數據包,我們是通過使用位元組流來傳輸資料的,所以當我們每傳送一個數據包,就順便附帶上資料包的長度,這樣就形成了“資料包頭”+“包體”的結構,包頭用來儲存資料包長度,包體用來儲存具體的資料,每當我們接受資料時,首先讀取資料包頭,得到資料長度,再和已經傳過來的長度對比,如果長度足夠,說明至少傳過來一個完整的包,我們就可以根據長度來取包體,如果不夠,我們先不讀取,直到長度滿足時,我們再把這條資料進行讀取。
這樣就能方便的解決分包粘包的問題,但是我們之前接受到的不完整的資訊放到哪呢?我們就需要一個快取區,如果資料不完整,我們先放在快取,當資料完整時,再取出讀取。這裡有一種建立快取區的方案,通過記憶體流來讀取。
快取區:我們使用記憶體流MemoryStream來進行讀寫操作,如果我們收到資料,我們就將資料寫入記憶體流,當記憶體流的長度滿足包頭長度時,就將訊息取出讀取,流的操作比較基本,可以查閱資料瞭解,這裡只提供一條思路。
既然說到了資料包,那我們接著把資料包說完,資料包頭+包體是我們最基本的結構,但我們的資料不可能就這樣來傳輸,因為一旦有人截獲我們的資料,就能輕易的修改我們的資料值,對遊戲造成影響,所以我們必須要對資料包進行加密。
一般情況下,我們會使用CRC進行冗餘驗證,看資料包是否傳輸完整,然後自定義自己的加密方式,將資料包加密以後再發出,有的專案還會對資料包進行壓縮,所以我們這裡給出一種通用的結構:資料頭(長度)+冗餘驗證(CRC)+是否壓縮+包體(加密後)。
這樣,我們就可以對包進行加密操作以及完整性驗證,保證我們的資料正確性、保密性和完整性。
接下來就是傳輸協議,我們傳輸的資料都是位元組流,那麼我們收到這些位元組流後如何處理呢?這就需要我們對資料包進行進一步編輯,給資料包一個協議ID,即把包體分為兩部分:包體=協議ID+內容;當我們收到資料後,首先進行拆包,獲取到協議ID後,就進行對應ID的事件派發,這裡我們會用到觀察者模式,下節會說到;我們派發事件後,對應的功能就會被執行,並且返回新的資料傳送到另一端。那麼我們通常都會有那些協議呢?
通常我們在Socket中,最不能缺少的協議就是心跳,心跳是檢驗客戶端和伺服器是否連線、是否斷線的非常有效的方法。伺服器會每隔一段時間給客戶端傳送一個心跳包,如果在一定時間內沒有收到客戶端的迴應,即認為客戶端已經掉線;同樣,如果客戶端在一定時間內沒 有收到伺服器的心跳包,則認為連線不可用。
除了心跳包,我們還會定義一些通用的包,如郵件、聊天等,具體要根據我們的需要來制定,比如我們要做一個MMORPG遊戲,那麼我們就有必要定義關於玩家位置、行為等相關的協議。
具體協議的實現,我們通常會用到類來接收包的資料,通常會有一個基類包,所有的協議類都繼承這個基類,但如果資料的複雜度不是很大的話,我們可以使用結構體,將原來的基類改為使用介面,這也是一種不錯的優化網路模組的方式。
總之,我們在遊戲開發中通常使用弱聯網進行登入驗證,強聯網用於遊戲內部,所以Socket的使用在遊戲開發中所佔比例以及重要程度不言而喻,這也是我們不斷研究、不斷探索、不斷優化的原因。下一節我們就具體講一講觀察者模式以及在網路模組中的應用。