C # отправляет объекты структуры через сокет - PullRequest
6 голосов
/ 18 декабря 2009

Я немного читал в программировании клиент / сервер на c #. я достаточно знаком с этим процессом, чтобы задать следующий вопрос:

как передать структурные объекты через tcp / ip, а не только через строки?

Мое приложение - сетевая игра с возможностями чата. поэтому вместо простой передачи текста я хотел бы применить структуру данных или структуру класса, которая будет иметь два поля: i. тип пакета ii. данные для типа пакета

и я передам это при необходимости во время выполнения приложения, декодирую объект данных на принимающей стороне и размещаю его там, где он принадлежит.

я не ищу код, просто некоторые идеи и поисковые запросы, которые я могу отправить в Google, так что я буду; иметь лучшее понимание.

Я читал о сериализации / де сериализации, это его путь?

спасибо.


Я проверил сообщения, которые показались как связанные темы, но все еще хотел бы получить дополнительную информацию.


Ответы [ 6 ]

7 голосов
/ 18 декабря 2009

В конечном итоге да: вы говорите о сериализации. Это может принимать разные формы, особенно в .NET, но в конечном итоге вам нужно выбрать между:

  • текст против двоичного файла; прямой двоичный файл стремится к тому, что будет меньше текста, так как обычно он требует меньше разбора и т. д .; Текст (xml, json и т. д.) обычно представлен в потоке как UTF8 (хотя возможна любая кодировка). Они в целом читаемы человеком, и, несмотря на то, что они более многословны, их обычно можно сжать довольно хорошо.
  • контракт против метаданных; основанные на контрактах сериализаторы фокусируются на представлении данных - предполагается, что другой конец канала понимает структуру, но не предполагается, что они совместно используют реализацию. Это имеет ограничения в том, что вы не можете внезапно ввести какой-то совершенно неожиданный подкласс, но делает его независимым от платформы. В отличие от этого, сериализаторы на основе метаданных отправляют в поток информацию type (т. Е. «Это экземпляр My.Namespace.FooBar). Это действительно облегчает работу, но редко работает между различными платформами (и часто не между версиями) - и вся эта информация о типах может быть многословной
  • руководство по сравнению с автоматическим; факт: ручные сериализаторы часто могут быть наиболее эффективными с точки зрения пропускной способности, так как вы можете вручную настроить черту из потока - но это требует лотов усилий и вам нужно понимать партии сериализации. Автоматические сериализаторы намного лучше для общего назначения (на самом деле: большинство сценариев). Избегайте ручного, если у вас нет выбора. Автоматические сериализаторы справляются со всеми сложностями, связанными с различными типами данных и т. Д.

Подходы к ручному сериализатору включают (только упоминая ключевое слово "сериализатор"): TextWriter, XmlWriter, IXmlSerializable, BinaryWriter, ISerializable. Ты не хочешь этого делать ...

Больше внимания уделяется автоматическим сериализаторам:

               | Contract               | Metadata
===============+========================+===========================
  Text         | XmlSerializer          | SoapFormatter
               | DataContractSerializer | NetDataContractSerializer
               | Json.NET               |
---------------+------------------------+---------------------------
  Binary       | protobuf-net           | BinaryFormatter

Поскольку вы говорите с необработанными потоками, я бы предпочел двоичный сериализатор на основе контракта - но тогда я написал protobuf-net , поэтому я могу быть предвзятым;

Для сравнения с обычными стеками RPC:

  • «удаленное взаимодействие» использует BinaryFormatter
  • Веб-сервисы "asmx" (включая WSE *) используют XmlSerializer
  • WCF может использовать много, чаще всего DataContractSerializer или NetDataContractSerializer, а иногда XmlSerializer (его также можно настроить, например, на protobuf-net)

Я могу с радостью написать пример использования protobuf-net в потоке для представления различных сообщений разных типов, но простой пример обработки сокетов с использованием protobuf-net приведен в одном из примеров проектов ( здесь, на самом деле )

6 голосов
/ 18 декабря 2009

Если вам не нужно богатство сериализации - если вы просто хотите записать структуру в байтовый массив, рассмотрите класс Marshal.

Например, рассмотрим приложение tar в C #. Формат tar основан на 512-байтовых блоках, а первый блок в серии имеет регулярную структуру. В идеале приложение хочет просто blitt данных из файла на диске, прямо в структуру . Метод Marshal.PtrToStructure делает это. Вот структура.

    [StructLayout(LayoutKind.Sequential, Size=512)]
    internal struct HeaderBlock
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
        public byte[]   name;    // name of file. 

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[]   mode;    // file mode

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[]   uid;     // owner user ID

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[]   gid;     // owner group ID

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
        public byte[]   size;    // length of file in bytes

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
        public byte[]   mtime;   // modify time of file

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[]   chksum;  // checksum for header

        // ... more like that... up to 512 bytes. 

Тогда вот общий класс, который выполняет блиттинг.

internal class RawSerializer<T>
{
    public T RawDeserialize( byte[] rawData )
    {
        return RawDeserialize( rawData , 0 );
    }    

    public T RawDeserialize( byte[] rawData , int position )
    {
        int rawsize = Marshal.SizeOf( typeof(T) );
        if( rawsize > rawData.Length )
            return default(T);

        IntPtr buffer = Marshal.AllocHGlobal( rawsize );
        Marshal.Copy( rawData, position, buffer, rawsize );
        T obj = (T) Marshal.PtrToStructure( buffer, typeof(T) );
        Marshal.FreeHGlobal( buffer );
        return obj;
    }

    public byte[] RawSerialize( T item )
    {
        int rawSize = Marshal.SizeOf( typeof(T) );
        IntPtr buffer = Marshal.AllocHGlobal( rawSize );
        Marshal.StructureToPtr( item, buffer, false );
        byte[] rawData = new byte[ rawSize ];
        Marshal.Copy( buffer, rawData, 0, rawSize );
        Marshal.FreeHGlobal( buffer );
        return rawData;
    }
}

Вы можете использовать этот класс с любой структурой. Вы должны использовать LayoutKind.Sequential и ограничивать себя типами blittable (в основном примитивами и массивами одного и того же) для использования этого подхода. Он быстрый и эффективный с точки зрения кода, производительности и памяти, но он немного ограничен в том, как его можно использовать.

Получив байтовый массив, вы можете передать его через NetworkStream и т. Д., А затем десериализовать, используя тот же класс на другом конце.

5 голосов
/ 18 декабря 2009

Сериализация - это самый простой способ, так как система поддерживает его напрямую. Однако есть некоторые проблемы с производительностью больших и сложных объектов. В вашем случае это звучит как сериализация это путь. Если вы хотите что-то более низкого уровня, вы можете проверить BinaryWriter / BinaryReader, который позволяет вам делать работу самостоятельно.

3 голосов
/ 18 декабря 2009

Вы можете создать NetworkStream на основе Socket и использовать любой механизм Stream для передачи ваших данных. Это переводит ваш вопрос на: Как я могу читать / записывать структуру из / в поток.

Вы можете использовать сериализацию, а также BinaryWriter / BinaryReader. Для небольшой структуры (как вы описываете) я бы написал несколько пользовательских методов:

var netStream = new NetworkStream(clientSocket, true);
var writer = new BinaryWriter(netStream);

writer.Write(data.Value1);
writer.Write(data.Value2);

Для более крупных конструкций я бы рассмотрел вариант Cheeso's Marshaling.

1 голос
/ 18 декабря 2009

Вы на правильном пути, используя сериализацию объектов.

Одна вещь, о которой я подумал, о которой я еще не упомянул, это то, что двоичные сериализаторы обычно создают меньше байтов для отправки через сокет, однако, если вы используете сериализатор XML или JSON, а затем сжимаете результаты с помощью CompressionStream (GZipStream?) Перед тем, как отправить его по сетевому потоку, вы можете получить размеры smalelr даже в зависимости от типа данных в вашем объекте (это лучше всего работает, когда у вас много строк).

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

1 голос
/ 18 декабря 2009

. Бинарная сериализация .NET, вероятно, будет самой быстрой из готовых опций, если предположить, что обе стороны механизма связи являются C # и могут загружать одну и ту же сборку, содержащую типы сообщений. Свернуть вашу собственную сериализацию, вероятно, просто прекрасно, если ваши структуры очень просты. Просто определите свою структуру данных в классе, а также метод для преобразования ее в строку и из нее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...