C # Отправка нескольких объектов через один сокет

#c# #sockets #serialization

#c# #сокеты #сериализация

Вопрос:

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

Клиент:

 var buffer = new byte[1024];
var binFormatter = new BinaryFormatter();
using (var ms = new MemoryStream())
{
    int bytesRead;
    while ((bytesRead = _clientReceive.Receive(buffer)) > 0);
    {
        ms.Write(buffer, 0, bytesRead);
    } 

    ms.Position = 0;
    message = (Message)binFormatter.Deserialize(ms);
} 
  

сервер:

 var gm = new Message { Message = "ObjectType", Object = Data };

using (var mem = new MemoryStream())
{
    var binFormatter = new BinaryFormatter();
    binFormatter.Serialize(mem, gm);
    var gmData = mem.ToArray();
    client.Send(gmData, gmData.Length, SocketFlags.None);
    client.Close();
}
  

но если я хочу отправить несколько сообщений (поместить полный клиентский код в цикл, а не вызывать client.Close() ), как я смогу определить, когда клиент получил полный объект? Помещение клиентского кода в цикл, подобный этому:

 while (_isReceivingStarted)
{
    var buffer = new byte[1024];
    var binFormatter = new BinaryFormatter();
    using (var ms = new MemoryStream())
    {
        int bytesRead;
        while ((bytesRead = _clientReceive.Receive(buffer)) > 0)
        {
            ms.Write(buffer, 0, bytesRead);
        }
        ms.Position = 0;
        message = (Message) binFormatter.Deserialize(ms);
    }
    // Do stuff then wait for new message
}
  

клиент зависнет на _clientReceive.Receive(buffer) , потому что он не получает Close () от клиента и никогда не получит 0 полученных байт. Если я закрою соединение на сервере, оно выполнит цикл, а затем выдаст ошибку при десериализации MemoryStream вместо блокировки на _clientReceive.Receive(buffer) в ожидании отправки другого объекта.

Я надеюсь, что это имеет смысл. Есть какие-нибудь указатели?

Ответ №1:

Я бы настоятельно рекомендовал посмотреть на Windows Communication Foundation. Он выполнит всю эту работу за вас, включая сериализацию и десериализацию ваших типов по проводам, через несколько настраиваемых каналов и т.д.

Комментарии:

1. Замена сокета на wcf — это переход между крайними значениями с точки зрения накладных расходов и т. Д

2. @Marc: В некоторой степени верно — если служебные данные очень важны в этом сценарии, то это может быть проблемой. Хотя, учитывая текущий код (открытие / закрытие соединений и выполнение всего с помощью синхронного кода сокета), воспринимаемые накладные расходы могут быть намного ниже…

3. Мне нужна быстрая связь с низкими накладными расходами, иначе WCF был бы превосходным. Размер большинства объектов составляет 600-800 байт, но время от времени появляется объект, размер которого может достигать 3 МБ. Мне нужно отправлять меньшие объекты со скоростью 20-100 в секунду.

Ответ №2:

При использовании такого сокета я бы использовал что-то вроде protobuf-net вместо BinaryFormatter (предостережение / раскрытие информации: я написал это) В частности, если вы записываете каждый элемент с Serializer.SerializeWithLenthPrefix(...) использованием Base128 для стиля префикса (один из вариантов) и какой-нибудь известный тег (другой вариант), такой как 1 , тогда на другом конце вы можете просто использовать DeserializeItems<Foo>(...) (снова указав Base128 и 1 ).

Это позволит корректно отбирать элементы без попыток повторного чтения до тех пор, пока поток не будет закрыт, когда это произойдет yield break . Каждый объект возвращается ( yield return ) отдельно, поэтому он не будет пытаться использовать весь поток, прежде чем предоставить вам данные.

Его также можно использовать с разнородными данными, если хотите, но проще всего использовать однородные.

Комментарии:

1. Хммм … это звучит очень похоже на то, что я хочу. Я разберусь с этим и вернусь! спасибо 🙂

2. @Joe в проекте быстрого запуска IIRC есть пример сокетов

3. @Joe и для справки, у protobuf почти всегда пропускная способность и процессор намного меньше, чем у BinaryFormatter

4. Спасибо! Я прошел, и все это отлично сработало, но я поговорил со своим профессором (это для школьного проекта), и он сказал, что, поскольку я не писал protobuf, я не могу его использовать : ( НО! Я определенно буду использовать это в других приложениях. Накладные расходы НАСТОЛЬКО малы! Я уменьшал свои 800-байтовые сериализованные массивы до <600, а мои 3 объекта были 2,5! Это потому, что ему не нужно кодировать ни одно из имен элементов?

5. @Joe правильно; сейчас… Интересно, понимает ли ваш профессор, что вы также не писали BinaryFormatter, Stream и т.д… Курсы — это причудливые среды, но здесь стоит обратить внимание на «NIH». В профессиональной работе «NIH» — дорогостоящее отношение 🙂

Ответ №3:

Вы могли бы отправить сообщение заголовка, которое информирует принимающую сторону о том, сколько байтов ожидать. Это похоже на директиву Content-Length в HTTP. Другой вариант, создайте пользовательскую строку завершения, которую вы отправляете в конце (очевидно, что она не может отображаться бит за битом нигде в двоичной полезной нагрузке сериализованных объектов, поэтому я бы выбрал первое решение).

Ответ №4:

Посмотрите на класс TcpListener:

http://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener.aspx

Или последуйте предложению Рида и загляните в WCF.

Ответ №5:

Если вы хотите пойти этим путем, вот несколько советов:

  • Отправка X количества байтов не гарантирует, что вы получите их в одном единственном Receive() на стороне клиента. У вас может быть MemoryStream на клиенте и буфер. Используйте буфер для приема и записи в MemoryStream, пока Receive не вернет 0.
  • Когда вы закончите отправку данных, используйте client.Завершение работы (SocketShutdown.Send) (или .Оба) перед вызовом Close(). Это предотвратит TCP RST на клиенте.
  • Если вы хотите сериализовать несколько объектов, просто сериализуйте их один за другим на сервере. Затем клиент буферизует все входящие данные и, когда Receive() возвращает 0, перемещает позицию в клиентском MemoryStream на 0 и начинает десериализацию объектов один за другим до ms.Позиция == мс.Длина.