О recv и буфере чтения - C Berkeley Sockets - PullRequest
9 голосов
/ 06 декабря 2010

Я использую сокеты Беркли и TCP (сокеты SOCK_STREAM).

Процесс:

  1. Я подключаюсь к удаленному адресу.
  2. Я отправляюсообщение ему.
  3. Я получаю от него сообщение.

Представьте, что я использую следующий буфер:

char recv_buffer[3000];
recv(socket, recv_buffer, 3000, 0);

Вопросы:

  • Как я могу узнать, что после первого вызова recv буфер чтения пуст или нет?Если он не пустой, мне придется снова вызывать recv, но если я сделаю это, когда он будет пустым, он будет блокироваться на длительное время.
  • Как узнать, сколько байтов я перечитал в recv_buffer?Я не могу использовать strlen, потому что полученное сообщение может содержать нулевые байты.

Спасибо.

Ответы [ 4 ]

12 голосов
/ 06 декабря 2010

Как я могу узнать, что после первого вызова recv буфер чтения пуст или нет?Если оно не пустое, мне придется снова вызывать recv, но если я сделаю это, когда оно пустое, оно будет блокироваться на длительное время.

Вы можете использовать select* 1007.* или poll системные вызовы вместе с вашим дескриптором сокета, чтобы сказать, есть ли данные, ожидающие чтения из сокета.

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

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

Как я могу узнать, сколько байтов я скопировал в recv_buffer?Я не могу использовать strlen, потому что полученное сообщение может содержать нулевые байты.

Системный вызов recv вернет число прочитанных байтов или -1, еслипроизошла ошибка.

На странице руководства для recv (2):

[recv] возвращает количество полученных байтов или -1, если произошла ошибка.Возвращаемое значение будет 0, когда узел выполнил упорядоченное завершение.

2 голосов
/ 06 декабря 2010

Как я могу узнать, что после первого вызова recv буфер чтения пуст или нет?

Даже в первый раз (после принятия клиента) recv может блокироваться и давать сбойесли клиентское соединение было потеряно.Вы должны либо:

  • использовать select или poll (сокеты BSD), либо какой-либо специфичный для ОС эквивалент, который может сказать вам, есть ли данные по конкретным дескрипторам сокета (а также исключение)условия и буферное пространство, в которое вы можете записать больше выходных данных)
  • вы можете установить сокет как неблокирующий, так что recv будет возвращать только то, что немедленно доступно (возможно, ничего)
  • выможет создать поток, который вы можете себе позволить, чтобы иметь блокированные данные recv, зная, что другие потоки будут выполнять другую работу, которая вас интересует, чтобы продолжить с

Как я могу знатьсколько байт я прочитал в recv_buffer?Я не могу использовать strlen, потому что полученное сообщение может содержать нулевые байты.

recv() возвращает количество прочитанных байтов или -1 при ошибке.

Обратите внимание, что TCP является протоколом потока байтов , что означает, что выгарантируется только возможность читать и записывать байты из него в правильном порядке, но границы сообщений не гарантируются.Таким образом, даже если отправитель произвел большую однократную запись в свой сокет, он может быть фрагментирован в пути и доставлен в несколько меньших блоков, или несколько меньших send() / write() s могут быть объединены и извлечены одним recv()/read().

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

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

0 голосов
/ 13 февраля 2011

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

0 голосов
/ 06 декабря 2010
  1. Если recv() возвращает менее 3000 байтов, то можно предположить, что буфер чтения был пуст.Если он возвращает 3000 байтов в вашем буфере 3000 байтов, то вам лучше знать, продолжать ли.Большинство протоколов включают некоторые вариации TLV - тип, длина, значение.Каждое сообщение содержит индикатор типа сообщения, некоторую длину (возможно, подразумеваемую типом, если длина является фиксированной) и значение.Если при чтении полученных вами данных вы обнаружите, что последний блок неполон, вы можете предположить, что есть еще что-то, что нужно прочитать.Вы также можете сделать сокет неблокирующим;тогда recv() завершится с ошибкой EAGAIN или EWOULDBLOCK, если не будет прочитано данных для чтения.

  2. Функция recv() возвращает количество прочитанных байтов.

...