2013-02-21 111 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!" 
} 

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