О recv и буфере чтения - C Berkeley Sockets

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

Процесс такой:

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

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

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

Вопросы следующие:

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

Спасибо.


person NeDark    schedule 06.12.2010    source источник


Ответы (4)


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

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

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

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

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

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

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

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

person Charles Salvia    schedule 06.12.2010
comment
Какое отношение имеет read(2) страница руководства к recv(2)? Они говорят похожие вещи, но лучше процитировать соответствующую страницу. - person Jonathan Leffler; 06.12.2010
comment
@Jonathan, когда тип дескриптора является сокетом, read совпадает с recv, за исключением того, что recv допускает дополнительный параметр flags. Но я отредактировал свой ответ, чтобы использовать recv, чтобы избежать путаницы. - person Charles Salvia; 06.12.2010
comment
Просто придирка к тонкому, предположительно непреднамеренному выводу: select / poll /, однако длина сообщения в заголовке ложно предполагает, что такие заголовки решают проблему блокировки, тогда как select / poll, неблокирующие сокеты или потоки должны использоваться в сочетании с заголовок длины сообщения или контрольные данные. - person Tony Delroy; 06.12.2010

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

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

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

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

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

Обратите внимание, что TCP является протоколом потока байтов, что означает, что вы гарантированно сможете читать и записывать из него байты в правильном порядке, но не гарантируется сохранение границ сообщения. Таким образом, даже если отправитель сделал одну большую запись в свой сокет, она может быть фрагментирована по пути и прибыть в несколько меньших блоков, или несколько меньших _6 _ / _ 7_ могут быть объединены и извлечены одним _8 _ / _ 9_.

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

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

person Tony Delroy    schedule 06.12.2010

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

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

person Jonathan Leffler    schedule 06.12.2010
comment
Не верно. Вы можете предположить, что буфер приема был опустошен этим чтением, но вы не можете предполагать, что данные впоследствии не поступили в него к тому времени, когда вы будете готовы вызвать recv (). - person user207421; 13.02.2011
comment
@EJP: «неправильно» - очень сильное утверждение - мне кажется очевидным, что вы не можете сказать, пришли ли данные с момента вашего вызова recv(), но, возможно, нужно указать на этот уровень базового, очевидного утверждения. - person Jonathan Leffler; 13.02.2011

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

person user207421    schedule 13.02.2011
comment
Функция ioctl() на самом деле не является частью стандарта POSIX, хотя она отображается как устаревший интерфейс в части STREAMS спецификации Single UNIX (см. ioctl (). Фактически, он доступен на большинстве платформ, производных от UNIX, но скорее зависит от платформы. - person Jonathan Leffler; 13.02.2011
comment
@Jonathan Leffler: согласен (не то, чтобы OP упоминал POSIX). FIONREAD или его варианты поддерживаются достаточно широко, так что Java может предоставлять available () для сокетов на всех своих платформах. - person user207421; 14.02.2011