通过tcp或套接字发送类型对象

我在为Xna制作的非常简单的游戏创建网络界面时遇到了麻烦。 我只需要通过TCP客户端/ Socket发送对象。 例如:我有一个名为“玩家”的class级。 在每个播放器中,都有一个字段名称“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应用程序但具有相当奇特的性质)引用完全相同的程序集。

在这种情况下,我们需要使用和替换forms的序列化和反序列化对象。 您还需要在两个世界(.NET和WP7或WP8)中相同地实现两组类。 您可以使用某种forms的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 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(); } } 

现在您可以像这样序列化对象:

 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(); } else if (message.Type == typeof(Enemy)) { Enemy enemy = message.Value.ToObject(); } //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  object and run this method when an incoming packet is received. NetworkComms.AppendGlobalIncomingPacketHandler("PlayerData", (packetHeader, connection, incomingPlayer) => { Console.WriteLine("Received player data. Player name was " + incomingPlayer.Name); //Do anything else with the player object here //eg 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!" } 

反序列化时,如果使用相同的设置,则将反序列化正确类型的对象。 正是我需要的! 希望这可以帮助。