#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.Позиция == мс.Длина.