Как определить, когда сообщение буфера протокола получено полностью? - PullRequest
6 голосов
/ 25 февраля 2009

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

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

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

Ответы [ 4 ]

14 голосов
/ 25 февраля 2009

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

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

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

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

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

6 голосов
/ 26 марта 2009

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

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

3 голосов
/ 01 марта 2009

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

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

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

1 голос
/ 01 марта 2009

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

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

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

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

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

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

...