2008-11-12 171 views
17

我想獲得對c#中tcp/ip套接字的更好的理解,因爲我想挑戰自己,看看我是否可以純粹爲了教育目的創建一個可用的MMO基礎結構(遊戲世界,地圖,玩家等)無意再成爲那些「OMGZ會讓我的r0x0r MMORPG比WoW更好」的人,你知道我在說什麼。c#中的多客戶端,異步套接字,最佳實踐?

無論如何,我想知道是否有人可以闡明如何設計這種系統以及需要什麼類型的東西,以及我應該注意什麼?

我最初的想法是將系統拆分爲獨立的客戶端/服務器連接,並與每個連接(在其自己的端口上)執行特定任務(例如更新播放器/怪物位置,發送和接收聊天消息等)對我來說會更輕鬆地處理數據,因爲你並不總是需要在數據上加一個頭部來知道數據包包含的信息。

這是有道理的,是有用的,或者我只是複雜的事情的方式嗎?

您的回覆非常感謝。

回答

30

如果你正在做的插座級編程,那麼不管你有多少端口打開每個消息類型,你仍然需要有某種形式的頭。即使它只是消息其餘部分的長度。話雖如此,將一個簡單的頭部和尾部結構添加到消息很容易。我認爲只需處理客戶端的一個端口就簡單多了。

我相信現代MMORPG遊戲(甚至舊的)有服務器的兩個層次。驗證您爲付費客戶端的登錄服務器。一旦確認這些信息會傳遞給包含所有遊戲世界信息的遊戲服務器。即便如此,這仍然只需要在客戶端有一個套接字開放,但並沒有禁止擁有更多。

此外大多數MMORPGS也加密所有的數據。如果你將這寫成練習以獲得樂趣,那麼這並不重要。

設計/一般書面協議,這裏的事情,我擔心:

端模式

是客戶端和服務器始終保證有相同的字節順序。如果沒有,我需要在我的序列化代碼中處理。處理永恆性有多種方式。

  1. 忽略它 - 顯然,一個壞的選擇
  2. 指定協議的字節序。這是舊協議所做/做的,因此術語網絡訂單總是大端的。它並不重要,只是你指定了哪一個或哪一個。如果你不使用大型的endianess,一些頑固的舊的網絡程序員會站起來,但是如果你的服務器和大多數客戶端是小端的話,那麼除了通過使協議變成大端,你實際上不會爲自己購買任何東西。
  3. 標記每個頭中的Endianess - 您可以添加一個cookie來告訴您客戶端/服務器的永久性,並根據需要對每個消息進行相應轉換。加班!
  4. 使你的協議不可知 - 如果你把所有的東西都發送成ASCII字符串,那麼endianess是無關緊要的,但是效率更低。

在4我通常會選擇2,並指定endianess是大多數客戶端,現在的日子將是小端。

向前和向後兼容

是否協議需要是向前和向後兼容。答案几乎總是肯定的。在這種情況下,這將決定我如何設計整個協議的版本控制,以及如何創建每個單獨的消息來處理實際上不應該成爲版本控制一部分的微小更改。你可以通過這種方式來使用XML,但是你會失去很多效率。

對於整體版本,我通常設計一些簡單的東西。客戶端發送版本信息,指定它說X.Y版本,只要服務器支持該版本,它就會發回一條確認客戶端版本的消息,並且所有內容都會繼續前進。否則它會暫停客戶端並終止連接。

對於每一個消息,你有類似如下:

+-------------------------+-------------------+-----------------+------------------------+ 
| Length of Msg (4 bytes) | MsgType (2 bytes) | Flags (4 bytes) | Msg (length - 6 bytes) | 
+-------------------------+-------------------+-----------------+------------------------+ 

長度明顯告訴你的消息是多久,不包括本身的長度。 MsgType是消息的類型。對於這個,只有兩個字節,因爲65356有很多應用程序的消息類型。這些標誌讓你知道消息中的序列化內容。這個字段與長度結合在一起就是你的向前兼容性和向後兼容性。

const uint32_t FLAG_0 = (1 << 0); 
const uint32_t FLAG_1 = (1 << 1); 
const uint32_t FLAG_2 = (1 << 2); 
... 
const uint32_t RESERVED_32 = (1 << 31); 

那麼你的反序列化的代碼可以執行類似如下:

uint32 length = MessageBuffer.ReadUint32(); 
uint32 start = MessageBuffer.CurrentOffset(); 
uint16 msgType = MessageBuffer.ReadUint16(); 
uint32 flags = MessageBuffer.ReadUint32(); 

if (flags & FLAG_0) 
{ 
    // Read out whatever FLAG_0 represents. 
    // Single or multiple fields 
} 
// ... 
// read out the other flags 
// ... 

MessageBuffer.AdvanceToOffset(start + length); 

這可以讓你添加新領域到底的消息,而不必修改整個協議。它還確保舊服務器和客戶端將忽略他們不知道的標誌。如果他們必須使用新的標誌和字段,那麼您只需更改總體協議版本。

使用框架的工作或沒有

有我會考慮使用的業務應用程序的各種網絡框架。除非我有特殊的需求,否則我會選擇標準框架。在你的情況下,你想學習套接字級編程,所以這個問題已經爲你解答。

如果你確實使用了框架,請確保它解決了上述兩個問題,或者至少在你需要在這些區域中定製它時阻礙你。

我是與第三方

處理在很多情況下,你可能會處理與第三方服務器/客戶端需要與溝通。這意味着一些場景:

  • 他們已經有一個協議定義 - 只需使用他們的協議。
  • 您已經定義了一個協議(他們願意使用它) - 再次簡單地使用定義的協議它們使用標準框架(基於WSDL等) - 使用框架。
  • 任何一方都沒有定義協議 - 嘗試根據手邊的所有因素(我在這裏提到的所有參數)以及他們的能力水平(至少據您所知)來確定最佳解決方案。無論如何確保雙方都同意並理解協議。從經驗來看,這可能是痛苦的或愉快的。這取決於你與誰一起工作。

無論哪種情況,您都不會與第三方合作,所以這只是爲了完整性而添加的。

我感覺好像我可以寫更多的關於這個,但它已經很長。我希望這會有所幫助,如果您有任何具體問題,請向Stackoverflow詢問。

一個編輯回答knoopx的問題:

+0

你能說出任何這些網絡框架嗎? – knoopx 2009-08-10 12:56:27

1

我會建議針對不同信息的多個連接。我將設計一些協議,其中包含一個包含要處理信息的標題。儘可能少使用資源。此外,您可能不希望將來自客戶端的各種職位和統計數據更新發送到服務器。否則,最終可能會導致用戶可以修改其發送回服務器的數據。

說一個用戶僞造怪物的位置以通過某物。我只會更新用戶的位置矢量和動作。讓其餘的信息由服務器處理和驗證。

0

是啊,我認爲你是正確的單一連接,當然還有客戶端不會是發送任何實際數據到服務器,更像就像「前進」簡單的命令,「左轉」等,服務器會移動地圖上的角色並將新的座標發送回客戶端。

2

我認爲你需要在你走路之前和跑步之前爬行。首先得到正確的框架和連接的設計,然後擔心可伸縮性。如果你的目標是學習C#和tcp/ip編程,那麼不要讓自己更難。

繼續您的初步想法,並保持數據流分開。

有樂趣和好運