2013-02-21 3 views
6

У меня возникли проблемы с созданием сетевого интерфейса для очень простой игры, которую я сделал в Xna. Мне просто нужно было отправлять объекты через TCP-клиент/Socket. Пример: У меня есть класс под названием «Игрок». В каждом игроке есть имя поля «Информация», типа «PlayerInfo». В клиенте/сервере мне нужно будет отправлять информацию каждому игроку каждому клиенту, кроме того, кто его отправил (очевидно). Это простой пример, но мне нужно будет сделать это примерно с 5 - 10 объектами, а также отправить обновления (позиции, действия ...) Есть ли простой способ сделать это с помощью TCP/Sock? Примечание: Я бы оценил свои знания в C# и программировании как 6/10, поэтому вам не нужно все объяснять, если у вас есть решение (пример: какая разница между переменной и полем). Я также знаю об интерфейсах, библиотеках и т. Д. Заранее благодарю!Отправлять типизированные объекты через tcp или сокеты

+0

Я бы сказал, что вам нужно создать собственное решение, высокопроизводительный механизм сериализации. Что-то вдоль линии бит-упаковки будет достаточно быстрым для небольших фрагментов информации. – Machinarius

+0

Поддерживает ли XNA WCF? Если так, то это будет путь. –

ответ

24

У меня есть один подход, который я бы рекомендовал, и два меньших, которые зависят от многих вещей.

Первый подразумевает, что вы уже знаете, как использовать класс Socket, но у вас много классов, которые вам нужно отправить через него.

С транспортной точки зрения вы должны создать/принять во внимание только один очень простой класс. Назовем этот класс MyMessage:

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

Ok. С точки зрения 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 

Если ваши классы содержат поля, относящиеся к типам, написанным другими (например, Microsoft), тогда их лучше было бы аннотировать с атрибутом. Большинство из тех, которые могут быть сериализованы, уже есть. Примитивные типы естественно сериализуемы. Вещи, которые не должно быть Сериализованные: потоковые видео, нитки, розетка и т.д.

После того, что у вас есть сериализуемые классы все, что вам нужно сделать, это сериализовать их экземпляры, отправлять их, получать их и десериализацию их:

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, но более универсальными в том, что характер среды выполнения, в которой размещаются ваши классы).

Вы можете прочитать о классе XmlSerializer на MSDN, здесь: http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx

+0

Все эти решения очень интересны, но единственная проблема, о которой я могу думать, - это выяснить, какой тип на стороне приемника? Если я десериализую что-нибудь, смогу ли я получить тип для трансляции? Спасибо за все ответы! Очень полезно. –

+0

Если вы используете подход BinaryFormatter, каким-то образом это будет так, как если бы два коллеги были в одном процессе. Короче: у вас есть классовая проблема, когда вы бросаете кучу классов в объект, а затем пытаетесь выяснить, что на самом деле есть объекты (Player, Movement, Interaction?). Поэтому, если вы используете что-то вроде if (obj is Player) {Player asPlayer = obj as Player;/* сделайте определенную обработку игрока */вы решите свой вопрос. Метод десериализации уже знает (во время выполнения), что вам нужно только для отправки (это игрок или нет, это ход и т. Д.). –

+0

Это просто логически говоря, Microsoft не могла догадаться, что вы в 2013 году напишите класс игрока и нуждаетесь в его десериализации. Таким образом, при синтаксическом, письменном, временном уровне, этот метод возвращает объект. –

3

Вы можете создать собственное решение, используя различные классы, предусмотренные в .net framework. Вы хотите проверить WCF или Sockets namepsace, в частности классы TcpClient и TcpListener, см. MSDN. Если вы выполняете поиск, связанный с их использованием, загружайте большие учебники. Вам также нужно будет рассмотреть, как превратить ваши типизированные объекты в массивы байтов, похожие на это question.

Альтернативный подход заключается в использовании сетевой библиотеки. Существуют библиотеки низкого уровня и библиотеки высокого уровня. Учитывая ваш уровень программирования и конкретную конечную цель, я бы предложил библиотеку высокого уровня. Примером такой сетевой библиотеки будет lidgren. Я разработчик другой сетевой библиотеки networkComms.net и быстрый пример того, как вы можете отправить типизированных объектов с помощью этой библиотеки следующим образом:

Shared Base (определяет объект 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; 
    } 
} 

Client (посылает один объект 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 с веб-сайта, чтобы вы могли добавить его в ссылку «using NetworkCommsDotNet». Также см. IP-адрес сервера в примере клиента в настоящее время «127.0.0.1», это должно работать, если вы запустили сервер и клиент на том же компьютере.

+0

Хм. Интересно. –

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

Все эти решения очень интересны, но единственная проблема, о которой я могу думать, - это выяснить, какой тип это на стороне приемника? Если я десериализую что-нибудь, смогу ли я получить тип для трансляции? Спасибо за все ответы! Очень полезно. –

+2

Метод, который я изложил, решает именно вашу проблему, а именно, как определить тип. Посмотрите, как я говорю 'message.Type == typeof (Player)' или 'message.Type == typeof (Enemy)' в примере? Вот как вы «извлекаете тип для трансляции». –

1

После более чем двух лет я нашел новые способы решения этой проблемы, и я думал, что обмен может быть кому-то полезен. Обратите внимание, что принятый ответ по-прежнему действителен.

Простейший способ сериализации типизированных объектов, которые я смог найти, - это использовать json-конвертер в Json.NET. Существует объект настроек, который позволяет хранить тип в json как значение с именем $type. Вот как это сделать, и в результате JSON:

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

JsonConvert.SerializeObject(myObject, settings); 

Json результат:

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

При десериализации, если используется тот же параметр, объект соответствующего типа будет десериализации. Именно то, что мне нужно! Надеюсь это поможет.

Смежные вопросы