2013-02-21 139 views
6

我在爲Xna製作的非常簡單的遊戲創建網絡接口時遇到了問題。我只需要通過TCP客戶端/套接字發送對象。例如: 我有一個名爲「Player」的類。 在每個玩家中,都有一個「PlayerInfo」類型的字段名稱「Info」。在客戶端/服務器中,我需要將每個玩家的信息發送給每個客戶端,除了發送它的人(顯然)。 這是一個簡單的例子,但我需要做大約5-10個對象,再加上發送播放器更新(位置,動作...) 有沒有一種簡單的方法來做到這一點與TCP/Sock? 注意:我會在C#和編程中評估我的知識爲6/10,因此,如果您有解決方案(例如:變量和字段之間的區別),則無需解釋所有內容。我也知道接口,庫等... 在此先感謝!通過tcp或套接字發送輸入的對象

+0

我想說你需要制定你自己的解決方案,一個高性能的序列化機制。比特封裝的某些東西對於小塊信息來說足夠快。 – Machinarius 2013-02-21 21:31:57

+0

XNA是否支持WCF?如果是這樣,那麼這將是一條路。 – 2013-02-21 21:57:51

回答

24

我有一個方法,我會建議和兩個較小的依賴於許多事情。

第一個意味着你已經知道如何使用Socket類,但有很多類需要通過它發送。

從運輸角度來看,您應該創建/考慮一個非常簡單的課程。我們稱之爲MyMessage類:

public class MyMessage { 
    public byte[] Data { get; set; } 
} 

好的。從TCP的角度來看,你所需要做的就是確保你能夠傳遞這個類的實例(從客戶端到服務器並返回)。我不會詳細介紹這樣做,但我會指出,如果你設法做到這一點,你可以將TCP/IP連接的性質從「字節流」轉換爲「消息流」。這意味着通常TCP/IP不能保證通過連接發送的數據塊以相同的格式到達目的地(它們可能連接在一起或分開)。它唯一保證的是,所有塊的字節最終將以相同的順序到達連接的另一端(總是)。

既然您已經啓動並運行了一個消息流,則可以使用.NET良好的舊序列化將任何類實例封裝在Data屬性中。它所做的就是將對象圖表序列化爲字節,反之亦然。

你這樣做(最常見)的方式是使用標準庫類: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter 可以在mscorlib.dll中發現,像這樣:

public static class Foo { 

    public static Message Serialize(object anySerializableObject) { 
    using (var memoryStream = new MemoryStream()) { 
     (new BinaryFormatter()).Serialize(memoryStream, anySerializableObject); 
     return new Message { Data = memoryStream.ToArray() }; 
    } 
    } 

    public static object Deserialize(Message message) { 
    using (var memoryStream = new MemoryStream(message.Data)) 
     return (new BinaryFormatter()).Deserialize(memoryStream); 
    } 

} 

BinaryFormatter類能夠遍歷從作爲Serialize(Stream,object)方法的第二個參數提供的根/標記開始的對象的樹/圖,並將所有原始值加上類型信息和相對位置信息寫入提供的流。 只要提供的流相應地定位到前一個對象圖序列化的位置,它也能夠完成對整個對象圖的反向和反序列化。

雖然這裏有一些捕獲:您將需要使用[SerializableAttribute]註釋所有類。如果你的類包含由您撰寫的其他類的領域,你說他們這樣做:

[SerializableAttribute] 
public class Player { 
    public PlayerInfo Info; 
    //... etc 

,那麼你需要註釋那些[SerializableAttribute]太:

[SerializableAttribute] 
public class PlayerInfo { //... etc 

如果你的類包含由其他人(比如微軟)寫的類型的字段,那麼這些字段最好已經用該屬性註釋。大部分可以序列化的已經是。原始類型是自然可序列化的。不應該序列化的事情是:文件流,線程,插座等

在確信你有序列化類所有你需要做的是序列化的情況下,送他們,接受他們和反序列化他們:

class Client { 

    public static void SendMovement(Movement movement) { 
    Message message = Foo.Serialize(movement); 

    socketHelper.SendMessage(message); 
    } 
    public static void SendPlayer(Player player) { 
    Message message = Foo.Serialize(player); 

    socketHelper.SendMessage(message); 
    } 
    // .. etc 

    public static void OnMessageReceivedFromServer(Message message) { 
    object obj = Foo.Deserialize(message); 
    if (obj is Movement) 
     Client.ProcessOtherPlayersMovement(obj as Movement); 
    else if (obj is Player) 
     Client.ProcessOtherPlayersStatusUpdates(obj as Player); 
    // .. etc 
    } 

    public static void ProcessOtherPlayersMovement(Movement movement) { 
    //... 
    } 
    // .. etc 

} 

雖然在服務器端:

class Server { 

    public static void OnMessageReceived(Message message, SocketHelper from, SocketHelper[] all) { 
    object obj = Foo.Deserialize(message); 
    if (obj is Movement) 
     Server.ProcessMovement(obj as Movement); 
    else if (obj is Player) 
     Server.ProcessPlayer(obj as Player); 
    // .. etc 

    foreach (var socketHelper in all) 
     if (socketHelper != from) 
     socketHelper.SendMessage(message); 
    } 
} 

您將需要一個共同的組件項目(類庫)由兩個可執行的項目(客戶端和服務器)引用。

所有需要傳遞的類都必須寫在該程序集中,以便服務器和客戶端都知道如何在這個非常詳細的級別上相互理解。

如果服務器不需要了解客戶端之間所說的話,並且只傳遞消息(向其他N-1客戶端廣播一條消息),那麼就忘記我對普通程序集所說的話。在這種特殊情況下,服務器只能看到字節,而客戶端更深入地瞭解來回發送的實際消息。

我說我有三種方法。

第二個涉及.NET Remoting,它可以讓你的肩膀承擔很多工作,但如果你不完全理解它,這很難實現。您可以在MSDN上閱讀以下內容:http://msdn.microsoft.com/en-us/library/kwdt6w2k(v=vs.100).aspx

只有當XNA的(現在或未來)XNA的意思是您的意思是Windows Phone或另一個不支持BinaryFormatter類的XNA實現時, ExEn與MonoTouch或其他)。 在這種情況下,如果你需要你的服務器(一個完整的,老式的.NET應用程序)來引用我所討論的常見程序集並且還有遊戲項目(這不會是一個很好的舊式.NET應用程序,但具有相當奇特的性質)引用完全相同的程序集。

在這種情況下,我們需要使用序列化和反序列化對象的替代形式。您還需要在兩個世界(.NET和WP7或WP8)中實現兩套類。您可以使用某種形式的XML序列化器,您需要明確地映射到類(不像BinaryFormatter類那樣強大,但在託管類的運行時的性質方面更通用)。

你可以閱讀MSDN上的XmlSerializer類,在這裏:http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx

+0

所有這些解決方案都非常有趣,但我能想到的唯一問題是如何找出它在接收端的類型?如果我反序列化了一些東西,我能否檢索出演員的類型? 感謝您的所有答案!很有幫助。 – 2013-02-21 23:24:20

+0

如果您使用BinaryFormatter方法,它會以某種方式看起來好像兩個對應方在同一個進程中。簡而言之:你有一個將一堆類拋入對象然後試圖弄清楚對象究竟是什麼的問題(玩家,移動,交互?)。所以如果你使用類似if(obj是Player){Player asPlayer = obj as Player;/*做特定的球員處理* /你會解決你的問題。反序列化方法已經知道(在運行時)你只需要發送什麼(它是否是玩家,它是一個動作等)。 – 2013-02-22 00:47:55

+0

從邏輯上講,微軟無法預測你會在2013年二月份寫出一個玩家類,並需要將其反序列化。因此,在語法,書面,設計時間級別,該方法返回對象。 – 2013-02-22 00:48:44

3

您可以使用.net框架中提供的各種類創建自己的解決方案。您可能想要簽出WCF或Sockets namepsace,特別是TcpClient和TcpListener類,請參閱MSDN。如果您執行與使用這些教程相關的搜索,則會有大量優秀的教程。您還需要考慮如何將類型化的對象轉換爲字節數組,類似於question

另一種方法是使用網絡庫。有低級庫和高級庫。鑑於你的編程經驗和具體的最終目標水平,我會建議一個高級圖書館。這種網絡庫的一個例子是lidgren。我另一個網絡庫networkComms.net以及如何您可以發送使用這個庫類型對象遵循一個簡單的例子開發商:

共享庫(定義Player對象):

[ProtoContract] 
class Player 
{ 
    [ProtoMember(1)] 
    public string Name { get; private set; } 
    [ProtoMember(2)] 
    public int Ammo { get; private set; } 
    [ProtoMember(3)] 
    public string Position { get; private set; } 

    private Player() { } 

    public Player(string name, int ammo, string position) 
    { 
     this.Name = name; 
     this.Ammo = ammo; 
     this.Position = position; 
    } 
} 

客戶端(發送一個Player對象):

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.IO; 

using NetworkCommsDotNet; 
using ProtoBuf; 

namespace Client 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Player player = new Player("MarcF", 100, "09.09N,21.12W"); 

      //Could also use UDPConnection.GetConnection... 
      TCPConnection.GetConnection(new ConnectionInfo("127.0.0.1", 10000)).SendObject("PlayerData", player); 

      Console.WriteLine("Send completed. Press any key to exit client."); 
      Console.ReadKey(true); 
      NetworkComms.Shutdown(); 
     } 
    } 
} 

服務器:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.IO; 

using NetworkCommsDotNet; 
using ProtoBuf; 

namespace Server 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      // Convert incoming data to a <Player> object and run this method when an incoming packet is received. 
      NetworkComms.AppendGlobalIncomingPacketHandler<Player>("PlayerData", (packetHeader, connection, incomingPlayer) => 
      { 
       Console.WriteLine("Received player data. Player name was " + incomingPlayer.Name); 
       //Do anything else with the player object here 
       //e.g. UpdatePlayerPosition(incomingPlayer); 
      }); 

      //Listen for incoming connections 
      TCPConnection.StartListening(true); 

      Console.WriteLine("Server ready. Press any key to shutdown server."); 
      Console.ReadKey(true); 
      NetworkComms.Shutdown(); 
     } 
    } 
} 

以上是此tutorial的修改版本。您顯然需要從網站下載NetworkCommsDotNet DLL,以便您可以將其添加到'使用NetworkCommsDotNet'參考中。另請參閱客戶端示例中的服務器IP地址當前爲「127.0.0.1」,如果您在同一臺計算機上同時運行服務器和客戶端,則此功能應起作用。

+0

嗯。有趣。 – 2013-02-21 22:09:14

8

我個人的快速和乾淨的解決方案,使用JSON.NET:

class JMessage 
{ 
    public Type Type { get; set; } 
    public JToken Value { get; set; } 

    public static JMessage FromValue<T>(T value) 
    { 
     return new JMessage { Type = typeof(T), Value = JToken.FromObject(value) }; 
    } 

    public static string Serialize(JMessage message) 
    { 
     return JToken.FromObject(message).ToString(); 
    } 

    public static JMessage Deserialize(string data) 
    { 
     return JToken.Parse(data).ToObject<JMessage>(); 
    } 
} 

現在你可以序列化你的對象,像這樣:

Player player = ...; 
Enemy enemy = ...; 
string data1 = JMessage.Serialize(JMessage.FromValue(player)); 
string data2 = JMessage.Serialize(JMessage.FromValue(enemy)); 

發送跨網的數據,然後在另一端,你可以這樣做:

string data = ...; 
JMessage message = JMessage.Deserialize(data); 
if (message.Type == typeof(Player)) 
{ 
    Player player = message.Value.ToObject<Player>(); 
} 
else if (message.Type == typeof(Enemy)) 
{ 
    Enemy enemy = message.Value.ToObject<Enemy>(); 
} 
//etc... 
+0

所有這些解決方案都非常有趣,但我能想到的唯一問題是如何找出它在接收端的類型?如果我反序列化了一些東西,我能否檢索出演員的類型? 感謝您的所有答案!很有幫助。 – 2013-02-21 23:14:51

+2

我概述的方法正好解決了您的問題,即如何確定類型。在示例中,請參閱我如何說'message.Type == typeof(Player)'或'message.Type == typeof(Enemy)'?這就是你「如何檢索演員陣容」。 – 2013-02-21 23:35:22

1

經過2年多的時間,我發現瞭解決這個問題的新方法,我認爲分享它可能對某人有用。請注意,接受的答案仍然有效。

我能找到的序列化類型對象的最簡單方法是在Json.NET中使用json轉換器。有一個設置對象允許您將該類型存儲在json中,其值爲$type。以下是如何做到這一點,得到的JSON:

JsonSerializerSettings settings = new JsonSerializerSettings 
{ 
    TypeNameHandling = TypeNameHandling.All 
}; 

JsonConvert.SerializeObject(myObject, settings); 

JSON結果:

{ 
    "$type" : "Testing.MyType, Testing", 
    "ExampleProperty" : "Hello world!" 
} 

反序列化時,如果使用相同的設置,正確類型的對象將被反序列化。正是我需要的!希望這可以幫助。