Как определить, когда сообщение буфера протокола получено полностью?

Это своего рода ответвление моего другого вопроса . Прочтите, если хотите, но это не обязательно.

В принципе, я понял, что для эффективного использования C# BeginReceive () для больших сообщений мне нужно либо (а) сначала прочитать длину пакета, а затем прочитать именно это количество байтов, либо (б) использовать разделитель конца пакета. У меня вопрос, присутствует ли что-нибудь из этого в буферах протокола? Я еще не использовал их, но, просматривая документацию, не похоже, что есть заголовок длины или разделитель.

Если нет, что мне делать? Должен ли я просто создать сообщение, а затем префикс / суффикс с заголовком длины / разделителем EOP?

Ответов (4)

Решение

Вам необходимо включить размер или конечный маркер в свой протокол. В сокеты на основе потоков (TCP / IP) ничего не встроено, кроме поддержки неопределенного потока октетов, произвольно разбитых на отдельные пакеты (и пакеты также могут быть разбросаны при передаче).

Простым подходом было бы, чтобы каждое «сообщение» имело заголовок фиксированного размера, включало в себя как версию протокола, так и размер полезной нагрузки, а также любые другие фиксированные данные. Затем содержимое сообщения (полезная нагрузка).

Optionally a message footer (fixed size) could be added with a checksum or even a cryptographic signature (depending on your reliability/security requirements).

Knowing the payload size allows you to keep reading a number of bytes that will be enough for the rest of the message (and if a read completes with less, doing another read for the remaining bytes until the whole message has been received).

Having a end message indicator also works, but you need to define how to handle your message containing that same octet sequence...

Я согласен с Мэттом в том, что заголовок лучше, чем нижний колонтитул для буферов протокола, по той основной причине, что, поскольку PB является двоичным протоколом, проблематично придумать нижний колонтитул, который также не был бы допустимой последовательностью сообщений. Многие протоколы на основе нижнего колонтитула (обычно EOL) работают, потому что содержимое сообщения находится в определенном диапазоне (обычно 0x20 - 0x7F ASCII).

Полезный подход, чтобы ваш низкий код уровня только буферов чтения от гнезда и представить их до кадрирования слоя , который собирает полные сообщения и запоминает частичные (я представляю асинхронную подход к этому (используя CCR) здесь , хотя для линейный протокол).

Для согласованности вы всегда можете определить свое сообщение как сообщение PB с тремя полями: фиксированное целое число как длина, перечисление как тип и последовательность байтов, которая содержит фактические данные. Это сохраняет весь ваш сетевой протокол прозрачным.

Пакеты TCP / IP, а также UDP содержат некоторую ссылку на их размер. Заголовок IP - содержит 16-битовое поле , которое задает длину заголовка IP и данные в байтах. Заголовка TCP содержит 4-битовое поле , которое задает размер заголовка TCP в 32-битных слов. Заголовка UDP , содержит 16-битовое поле , которое задает длину заголовка UDP и данных в байтах.

Вот в чем дело.

Используя стандартные стандартные сокеты в Windows, независимо от того, используете ли вы пространство имен System.Net.Sockets в C# или собственный материал Winsock в Win32, вы никогда не увидите заголовки IP / TCP / UDP. Эти заголовки удаляются, поэтому при чтении сокета вы получаете фактическую полезную нагрузку, то есть данные, которые были отправлены.

Типичный образец из всего, что я когда-либо видел и делал с использованием сокетов, заключается в том, что вы определяете заголовок уровня приложения, который предшествует данным, которые вы хотите отправить. Как минимум, этот заголовок должен включать размер последующих данных. Это позволит вам прочитать каждое «сообщение» целиком, не догадываясь о его размере. Вы можете придумать что угодно, например, шаблоны синхронизации, CRC, версию, тип сообщения и т. Д., Но размер «сообщения» - это все, что вам действительно нужно.

And for what it's worth, I would suggest using a header instead of an end-of-packet delimiter. I'm not sure if there is a signficant disadvantage to the EOP delimiter, but the header is the approach used by most IP protocols I've seen. In addition, it just seems more intuitive to me to process a message from the beginning rather than wait for some pattern to appear in my stream to indicate that my message is complete.

РЕДАКТИРОВАТЬ: Я только что узнал о проекте Google Protocol Buffers. Насколько я могу судить, это схема двоичной сериализации / десериализации для WCF (я уверен, что это грубое упрощение). Если вы используете WCF, вам не нужно беспокоиться о размере отправляемых сообщений, потому что сантехника WCF позаботится об этом за кулисами, поэтому, вероятно, вы не нашли ничего, связанного с длиной сообщения в протоколе. Документация по буферам. Однако в случае розеток знание размера очень поможет, как обсуждалось выше. Я предполагаю, что вы сериализуете свои данные, используя буферы протокола, а затем прикрепите любой заголовок приложения, который вы придумаете, перед его отправкой. На стороне приема вы снимаете заголовок, а затем десериализуете оставшуюся часть сообщения.

Приносим извинения за опоздание на вечеринку. Я автор protobuf-net, одной из реализаций C#. Для использования в сети вам следует рассмотреть методы «[De] SerializeWithLengthPrefix» - так он автоматически обработает длину за вас. Примеры есть в источнике.

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