簡體   English   中英

通過tcp或套接字發送類型對象

[英]Send typed objects through tcp or sockets

我在為Xna制作的非常簡單的游戲創建網絡界面時遇到了麻煩。 我只需要通過TCP客戶端/ Socket發送對象。 例如:我有一個名為“玩家”的班級。 在每個播放器中,都有一個字段名稱“Info”,類型為“PlayerInfo”。 在客戶端/服務器中,我需要將每個玩家的信息發送給每個客戶,除了發送它的人(顯然)。 這是一個簡單的例子,但我需要用大約5-10個對象來做,再加上發送玩家更新(位置,動作......)有沒有一種簡單的方法來做TCP / Sock? 注意:我會將我的知識評為C#並編程為6/10,因此如果您有解決方案,則無需解釋所有內容(例如:變量和字段之間的區別)。 我也知道接口,庫等...提前謝謝!

我有一種方法可以推薦,兩種較小的方法依賴於很多東西。

第一個暗示您已經知道如何使用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)方法的第二個參數提供的root / sentinel開始的對象的樹/圖,並將所有原始值加上類型信息和相對位置信息寫入提供的流。 只要提供的流相應於前一個對象圖序列化的位置,它也能夠完全反向並反序列化整個對象圖。

這里有一些捕獲:您需要使用[SerializableAttribute]注釋所有類。 如果您的類包含由您編寫的其他類的字段,並且您說他們這樣做:

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

那么你需要用[SerializableAttribute]注釋那些:

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

如果您的類包含由其他人(比如Microsoft)編寫的類型的字段,則最好使用該屬性對這些字段進行注釋。 大多數可以序列化的是。 原始類型是自然可序列化的。 不應該序列化的東西是:FileStreams,Threads,Sockets等。

在確保您具有可序列化的類之后,您需要做的就是序列化它們的實例,發送它們,接收它們並反序列化它們:

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你的意思是Windows Phone或XNA的另一個實現,它不支持BinaryFormatter類(ExEn與MonoTouch,或其他)。 在這種情況下,如果你需要你的服務器(一個完整的,老式的.NET應用程序)來引用我所談到的公共程序集並且還有游戲項目(這不是一個很好的老式),你會很難。 NET應用程序但具有相當異國情調的性質)引用完全相同的程序集。

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

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

我的個人快速清潔解決方案,使用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...

您可以使用.net框架中提供的各種類創建自己的解決方案。 您可能想要簽出WCF或套接字namepsace,特別是TcpClient和TcpListener類,請參閱MSDN 如果您進行與使用這些相關的搜索,那么有很多很棒的教程。 您還需要考慮如何將類型化對象轉換為字節數組,類似於這個問題

另一種方法是使用網絡庫。 有低級庫和高級庫。 鑒於您的編程經驗水平和特定的最終目標,我建議使用高級庫。 這種網絡庫的一個例子就是蓋子 我是另一個網絡庫networkComms.net的開發人員,以及如何使用此庫發送類型對象的快速示例如下:

共享基礎(定義播放器對象):

[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();
        }
    }
}

以上是本教程的修改版本。 您顯然需要從網站下載NetworkCommsDotNet DLL,以便您可以在“使用NetworkCommsDotNet”參考中添加它。 另請參閱客戶端示例中的服務器IP地址當前為“127.0.0.1”,如果您在同一台計算機上同時運行服務器和客戶端,這應該可以工作。

經過兩年多的時間,我找到了解決這個問題的新方法,我認為分享它可能對某些人有用。 請注意,接受的答案仍然有效。

序列化類型對象的最簡單方法是在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!"
}

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM