Как действует C ++ `recv` функция при получении данных? Может ли он получить частичный «пакет»? - PullRequest
4 голосов
/ 14 февраля 2012
static void HandlePackets(void* pParams)
{
   int iResult = 0;
   char recvbuf[MAX_PACKET_LENGTH];

   printf("Packet handling started\n");

   while((iResult = recv(lhSocket, recvbuf, MAX_PACKET_LENGTH, 0)) > 0)
      printf("Bytes received: %d\n", iResult);

   printf("Packet handling stopped with reason %i", WSAGetLastError());
}

На данный момент выводится только количество полученных байтов.

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

Например, сервер отправил один пакет длиной 512 байт. Возможно ли, что recv сначала получил 500 байт, а оставшиеся 12 получат со второй попытки?

Если сервер отправляет много пакетов длиной 512 байтов для каждого, возможно ли, что recv получит 700 байтов при первом выполнении, а оставшиеся байты - во втором?

MAX_PACKET_LENGTH составляет 1024

(я говорю здесь о пакетах прикладного уровня, а не транспортного уровня.)

Вся проблема в том, нужно ли предоставить клиенту некоторую возможность объединять полученные байты в один пакет или разбивать переполненные байты на разные пакеты?

Ответы [ 3 ]

4 голосов
/ 14 февраля 2012

Возможно ли, что recv сначала получил 500 байтов, а оставшиеся 12 получат со второй попытки?

Да, определенно.

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

Вся проблема в том, нужно ли предоставить клиенту некоторую возможность объединять полученные байты в один пакет или разбивать переполненные байты на разные пакеты?

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

TCP / IP не знает протокол вашего приложения; Как сказал Дэвид, если вы хотите разделить входящий поток данных на «пакеты», то вы должны сделать это самостоятельно.

4 голосов
/ 14 февраля 2012

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

Сетевой стек знает TCP, но не знает протокол , который вы реализуете. Если вы хотите разделить поток байтов на сообщения, это ваша работа.

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

Обратите внимание, что это правило для TCP и других протоколов байтового потока. Другие протоколы могут иметь различную семантику.

2 голосов
/ 14 февраля 2012

В соединении TCP отправитель использует write() (возможно, в цикле) для отправки данных.На стороне получателя read() копирует полученные данные из буфера сокета в ваш буфер на уровне приложения.Если один метод write () отправит, скажем, 900 байтов, TCP может разбить его на несколько кусков разных размеров ... например, 300, 400 и 200 байтов, поэтому на принимающей стороне необходимо три раза вызвать read(), чтобыполучить все данные.

Теперь, если вы поместите recv() в цикл и каждый раз, когда он заполняет весь буфер или его часть, как вы узнаете, когда прекратить прием?Когда отправитель отправляет все данные и изящно закрывает соединение, ваш recv() вернет 0.Больше нечего получить, вы можете закрыть розетку.

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

Но вернемся к циклу recv().Помимо ожидания 0, есть еще одна хитрость того, как получатель может знать, когда прекратить прием: отправитель и получатель могут договориться (знать), что, например, первые два отправленных байта будут содержать длину сообщения .Поэтому в начале получатель будет ждать только два байта.Получив их, он распакует информацию о размере сообщения, скажем, 900 байтов.Теперь получатель может настроить свой размер буфера до 900 и принимать в цикле, пока не будут получены все 900 байтов.Каждый recv() будет возвращать количество полученных байтов, и получатель может продвигать указатель буфера на это количество байтов, поэтому следующий recv() записывает в свободную часть буфера.

Кстати, эти общие знания (контракт) между клиентом и сервером (или получателем и отправителем) являются вашим протоколом связи на уровне приложения.Он поставляется поверх протокола TCP.

...