2009-02-25 2 views
6

Это своего рода ответвление от моего other question. Прочтите, если хотите, но это не обязательно.Как определить, когда сообщение о протокольном буфере получено полностью?

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

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

ответ

14

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

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

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

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

Наличие индикатора конца сообщения также работает, но вы должны определить, как обрабатывать ваше сообщение, содержащее что же последовательности октетов ...

1

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

Вот что.

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

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

И для чего это стоит, я бы предложил использовать заголовок вместо разделителя конца пакета. Я не уверен, есть ли существенный недостаток для разделителя EOP, но заголовок - это подход, используемый большинством IP-протоколов, которые я видел.Кроме того, мне просто кажется более интуитивным, чтобы обрабатывать сообщение с самого начала, а не ждать появления некоторого шаблона в моем потоке, чтобы указать, что мое сообщение завершено.

EDIT: Я только что узнал о проекте «Буферы протоколов Google». Из того, что я могу сказать, это двоичная схема сериализации/де-сериализации для WCF (я уверен, что это грубое упрощение). Если вы используете WCF, вам не нужно беспокоиться о размере отправляемых сообщений, потому что WCF-сантехника позаботится об этом за кулисами, что, вероятно, почему вы не обнаружили ничего, связанного с длиной сообщения в протоколе Буферная документация. Однако, в случае сокетов, знание размера поможет чрезвычайно, как обсуждалось выше. Я предполагаю, что вы будете сериализовать свои данные с помощью протокольных буферов, а затем примените любой заголовок приложения, который вы придумали, прежде чем отправлять его. На стороне приема вы удалите заголовок, а затем де-сериализуете оставшуюся часть сообщения.

+0

Если вы имеете в виду protobuf-net, то это не только для WCF; в проекте есть примеры сокетов. –

3

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

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

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

6

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

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

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