Разве recv() не блокирует программирование сокетов C?

В приемнике у меня есть

recvfd=accept(sockfd,&other_side,&len);
while(1)
{
   recv(recvfd,buf,MAX_BYTES-1,0);
   buf[MAX_BYTES]='\0';
   printf("\n Number %d contents :%s\n",counter,buf);
   counter++;
}

В Sender у меня есть

send(sockfd,mesg,(size_t)length,0);
send(sockfd,mesg,(size_t)length,0);
send(sockfd,mesg,(size_t)length,0);

MAX_BYTES — 1024, а длина сообщения — 15. В настоящее время он вызывает recv только один раз. Я хочу, чтобы функция recv вызывалась три раза для каждой соответствующей отправки. Как мне этого добиться?


person sattu    schedule 07.06.2013    source источник
comment
Это невозможно: recv не знает, какой размер порции данных! Вы можете сделать размер буфера равным только length.   -  person Eddy_Em    schedule 07.06.2013
comment
Возможно, вы используете TCP. TCP не сохраняет никаких границ сообщений. Это просто дает вам поток для работы. Вы не можете заставить recv() вести себя так, как хотите.   -  person nos    schedule 07.06.2013
comment
Основное правило заключается в том, что вы не должны ожидать, что данные будут исходить из TCP-соединения в виде кусков того же размера, что и на другом конце. Иногда это может быть просто вопросом временного совпадения - полагаясь на это, вы только внесете ошибки, которые нарушат работу в реальном мире, несмотря на то, что иногда кажется, что они работают в вашей тестовой системе.   -  person Chris Stratton    schedule 07.06.2013


Ответы (5)


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

Простой протокол кадрирования должен иметь некоторое поле длины (скажем, два октета: 16 бит, максимальная длина кадра 65535 байт). За полем длины следует ровно столько байтов.

Вы даже не должны предполагать, что само поле длины получено сразу. Вы можете запросить два байта, но recv может вернуть только один. Этого не произойдет для самого первого сообщения, полученного из сокета, потому что сегменты сети (или локального IPC-канала, если уж на то пошло) никогда не бывают длиной всего в один байт. Но где-то в середине потока первый байт 16-битного поля может оказаться на последней позиции одного сетевого кадра.

Простой способ справиться с этим — использовать буферизованную библиотеку ввода-вывода вместо необработанных файловых дескрипторов операционной системы. В среде POSIX вы можете взять дескриптор открытого сокета и использовать функцию fdopen, чтобы связать его с потоком FILE *. Затем вы можете использовать такие функции, как getc и fread, чтобы упростить обработку ввода (несколько).

Если внутриполосное кадрирование неприемлемо, вам следует использовать протокол, который поддерживает кадрирование, а именно сокеты типа дейтаграммы. Основным недостатком этого является то, что основным протоколом на основе дейтаграмм, используемым через IP, является UDP, а UDP ненадежен. Это усложняет ваше приложение для работы с неупорядоченными и отсутствующими кадрами. Размер фреймов также ограничен максимальным размером дейтаграммы IP, который составляет около 64 килобайт, включая все заголовки протокола.

Большие дейтаграммы UDP фрагментируются, что при ненадежности сети приводит к еще большей ненадежности: если какой-либо IP-фрагмент потерян, весь пакет теряется. Все это должно быть ретранслировано; нет возможности просто получить повторение утраченного фрагмента. Протокол TCP выполняет «обнаружение MTU пути», чтобы настроить размер своего сегмента, чтобы избежать фрагментации IP, и TCP имеет выборочную повторную передачу для восстановления отсутствующих сегментов.

person Kaz    schedule 07.06.2013

Короче говоря: да, это блокирует. Но не так, как вы думаете.

recv() блокируется до тех пор, пока какие-либо данные не станут доступны для чтения. Но вы не знаете размер заранее.

В вашем сценарии вы можете сделать следующее:

  1. вызовите select() и поместите сокет, из которого вы хотите читать, в набор READ FD
  2. когда select() возвращается с положительным числом, данные вашего сокета готовы к чтению
  3. затем проверьте, можете ли вы получить length байта из сокета:
    recv(recvfd, buf, MAX_BYTES-1, MSG_PEEK), см. man recv(2) параметр MSG_PEEK или посмотрите в MSDN, там он тоже есть
  4. теперь вы знаете, сколько данных доступно
  5. если доступно меньше length, вернуться и ничего не делать
  6. если доступно не менее length, прочитать length и вернуться (если доступно более length, мы продолжим с шага 2, так как новое событие READ будет сигнализировано select()
person eckes    schedule 07.06.2013

Бьюсь об заклад, вы создали TCP-сокет, используя SOCK_STREAM, что приведет к чтению трех сообщений в ваш буфер во время первого вызова recv. Если вы хотите читать сообщения одно за другим, создайте сокет UPD с помощью SOCK_DGRAM или разработайте какой-либо тип формата сообщений, который позволит вам анализировать ваши сообщения, когда они поступают в потоке (при условии, что ваши сообщения не всегда будут фиксированной длины). ).

person ryanbwork    schedule 07.06.2013
comment
Даже если у меня есть формат сообщения об исправлении, мне нужно разобрать его только после того, как буфер заполнится. Правильный? - person sattu; 07.06.2013
comment
Если ваши сообщения имеют фиксированную длину, вы читаете данные из сокета в свой буфер, анализируете только полные сообщения в буфере (поскольку в буфере может быть несколько сообщений) и оставляете любые частичные сообщения в буфере для последующего чтения. . - person Remy Lebeau; 07.06.2013
comment
Нет, как прокомментировал Реми другой ответ, recv вернется, когда будут доступны какие-либо данные. Если вы используете сокеты TCP, я бы посоветовал читать из сокета, используя буфер, размер которого соответствует вашему сообщению фиксированной длины, чтобы добиться желаемого поведения. - person ryanbwork; 07.06.2013
comment
Правильно, однако будьте осторожны, рассматривая UDP как альтернативу, созданную разработчиком, что UDP не гарантирует, что пакеты будут доставлены, не информирует вас, если доставка не удалась, и даже имеет (по крайней мере, теоретический) риск доставки пакетов в порядке, отличном от того, в котором они были отправлены. - person Chris Stratton; 07.06.2013

Сначала отправьте length для получения в фиксированном формате относительно размера length в байтах, которые вы используете для передачи этого length, затем выполните цикл recv(), пока не будет получено length байт.


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

Прочтите справочные страницы для recvи send. Особенно читайте разделы о том, что это за функции RETURN.

person alk    schedule 08.06.2013

recv будет блокироваться до тех пор, пока не будет заполнен весь буфер или сокет не будет закрыт.

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

Вы можете использовать select, чтобы определить,

  1. есть байты, ожидающие чтения,
  2. сколько байтов ожидает чтения, затем
  3. читать только эти байты

Это поможет избежать блокировки recv.

Изменить:

После повторного прочтения документов следующее может быть правдой: ваши три «сообщения» могут быть прочитаны одновременно с length + length + length < MAX_BYTES - 1.

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

person Matt Houser    schedule 07.06.2013
comment
Это не совсем правда. recv() будет блокироваться до тех пор, пока не станут доступны какие-либо данные, а затем возвращает количество фактически полученных байтов, не превышающее запрошенное количество байтов, поэтому возможно (даже вероятно), что recv() возвращает меньше байтов, чем запрошено. Он не ждет, пока прибудет весь запрошенный номер перед выходом. Вот почему очень важно обращать внимание на фактическое возвращаемое значение и снова вызывать recv(), если ожидаются дополнительные байты. - person Remy Lebeau; 07.06.2013
comment
Google делает паршивую работу, показывая выдержки из этого ответа в верхней части результатов поиска, лол. - person Superziyi; 26.04.2020