Должен ли я вручную вставлять информацию о размере данных в передачу TCP? - PullRequest
7 голосов
/ 16 февраля 2010

Представьте, что вы и я отправляем довольно длинное предложение (скажем, 1024000 байт) через TCP.

Если вы напишите мне предложение размером 1024000 байт, вы фактически используете NetworkStream для записи этих байтов.

Когда я получу, должен ли я заранее знать размер отправленного вами предложения?

Если нет, как я могу проверить, когда мне следует остановить поток? Read?

Если да, должна ли программа иметь средства, которые встраивают размер данных в заголовок данных? Итак, я сначала получаю 4 байта, чтобы узнать, сколько всего я должен прочитать?

Есть ли в .Net что-нибудь для автоматического встраивания размера данных в передачу?

Ответы [ 9 ]

4 голосов
/ 16 февраля 2010

Ни в .NET, ни в протоколе TCP ничего не встроено, чтобы определить размер сообщения, которое должно прийти заранее. Протокол TCP только указывает, что все данные будут переданы в конечную точку-получатель (или, по крайней мере, для этого будут приложены все усилия).

Вы несете единоличную ответственность за определение способа, позволяющего получателю знать, сколько данных нужно прочитать. Детали того, как вы это делаете, - как уже указывали другие, - зависят от характера того, что вы переносите: вы можете отправить длину сначала, как вы упомянули, вы можете кодировать специальные последовательности, называемые терминаторами, вы можете использовать предопределенные блоки данных поэтому все сообщения имеют одинаковый размер и т. д.

EDIT

Это началось как комментарий, но это больше, чем соответствует этому ограничению.

К добавить NULL в поток просто означает добавление символа с двоичным значением 0 (не путать с символом 0). В зависимости от кодировки, которую вы используете для передачи (например, ASCII, UTF-8, UTF-16 и т. Д.), Которая может переводиться в отправку одного или более 0 байтов, но если вы используете соответствующий перевод, вам просто нужно поместить что-то вроде \0 в вашей строке. Вот пример:

string textToSend = "This is a NULL Terminated text\0";
byte[] bufferToSend = Encoding.UTF8Encoding.GetBytes(textToSend);

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

2 голосов
/ 16 февраля 2010

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

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

1 голос
/ 16 февраля 2010

Суть в том, что с TCP не существует соответствия между количеством и размером операций записи на сокете на стороне передачи и количеством считываний числа / размера на сокете на стороне получателя.

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

Каждый раз, когда мне приходилось решать эту проблему, я использовал некоторую комбинацию:

a) используйте магическое число, чтобы указать начало или конец сообщения данных (или обоих)

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

c) использовать поле длины после начального магического номера(при условии, что передающая сторона знает длину данных до передачи начинается)

Однако, прежде чем перейти кхорошо посмотрим, какие библиотеки протоколов более высокого уровня реализованы для языка / платформы, которую вы используете.NetworkStream?Это Windows API / MFC или что-то в этом роде.

Например, недавно мне пришлось настроить систему клиент / сервер.Функциональность клиент-сервер уже была написана на python, поэтому простое использование python xmlrpclib / server сделало абсолютно простым объединение двух программ - буквально скопируйте пример, и я сделал это за 30 минут.Если бы я сам запрограммировал какой-нибудь протокол подделки прямо на tcp, это было бы 5 дней!

1 голос
/ 16 февраля 2010

Если вы знаете или можете легко узнать общую длину сообщения, я бы посоветовал передать его заранее. Если это невозможно или очень дорого определить, вы можете использовать что-то похожее на chunked Transfer Encoding в HTTP.

1 голос
/ 16 февраля 2010

Есть два способа сделать это, один из которых вы описали, поместив размер сообщения в заголовок, а другой - поместить какой-нибудь маркер завершения в поток. Например, если ваше сообщение гарантированно не содержит NUL символов, вы можете завершить его с помощью NUL.

1 голос
/ 16 февраля 2010

Когда я получу, должен ли я заранее знать размер отправленного вами предложения?

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

Если нет, как я могу проверить, когда мне следует остановить поток? Read?

Содержимое вашего потока определяет это. Например, многие сообщения кодируют некоторую информацию, которая сообщает вам, что это сообщение окончено (например, нулевой байт для представления конца строки или </html> для представления конца документа HTML).

0 голосов
/ 16 февраля 2010

Вы также можете изучить классы BinaryReader / BinaryWriter, которые можно обернуть вокруг любого потока, TCP или иным образом.

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

0 голосов
/ 16 февраля 2010

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

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

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

0 голосов
/ 16 февраля 2010

Поскольку TCP является надежным протоколом, вы можете либо структурировать свой протокол, чтобы указать количество поступающих байтов, либо использовать какой-либо терминатор для указания конца передачи. Если бы вы использовали UDP, который не гарантированно надежен, было бы гораздо важнее либо создать протокол, который будет выдерживать отброшенные байты, либо указывать ожидаемое количество байтов (и иметь механизм повторной передачи), поскольку пакет, содержащий завершение может быть потеряно Также могут быть полезны максимальное время передачи данных и таймауты, но только если вы можете определить разумный максимум.

...