Насколько большой должен быть мой буфер recv при вызове recv в библиотеке сокетов

У меня есть несколько вопросов о библиотеке сокетов в C. Вот фрагмент кода, о котором я расскажу в своих вопросах.

char recv_buffer[3000];
recv(socket, recv_buffer, 3000, 0);
  • Как мне решить, насколько велика сделать recv_buffer? Я использую 3000, но это произвольно.
  • что произойдет, если recv() получит пакет больше моего буфера?
  • как я могу узнать, получил ли я все сообщение без повторного вызова recv и его ждут всегда, когда ничего не получится?
  • Есть ли способ сделать буфер не имеющим фиксированного пространства, так что я могу продолжать добавлять его, не опасаясь исчерпать пространство? возможно, используя strcat, чтобы связать последний ответ recv() с буфером?

Я знаю много вопросов в одном, но я бы очень признателен за любые ответы.

Ответ 1

Ответы на эти вопросы различаются в зависимости от того, используете ли вы сокет потока (SOCK_STREAM) или сокет datagram (SOCK_DGRAM) - в TCP/IP, первый соответствует TCP, а второй - UDP.

Как вы знаете, насколько велик буфер, переданный в recv()?

  • SOCK_STREAM: Это не имеет большого значения. Если ваш протокол является транзакционным/интерактивным, просто выберите размер, который может содержать наибольшее индивидуальное сообщение/команду, которые вы бы разумно ожидали (3000, вероятно, прекрасен). Если ваш протокол передает объемные данные, то более крупные буферы могут быть более эффективными - хорошее эмпирическое правило примерно такое же, как размер принимаемого буфера ядра сокета (часто около 256 кБ).

  • SOCK_DGRAM: Используйте буфер, достаточно большой для хранения самого большого пакета, который когда-либо отправляет ваш протокол на уровне приложения. Если вы используете UDP, тогда в общем случае ваш протокол уровня приложения не должен отправлять пакеты размером более 1400 байт, потому что их, безусловно, нужно будет фрагментировать и повторно собрать.

Что произойдет, если recv получит пакет больше, чем буфер?

  • SOCK_STREAM: вопрос на самом деле не имеет смысла, потому что потоковые сокеты не имеют понятия пакетов - это просто непрерывный поток байтов. Если для чтения имеется больше байтов, доступных для вашего буфера, тогда они будут поставлены в очередь ОС и доступны для следующего вызова recv.

  • SOCK_DGRAM: избыточные байты отбрасываются.

Как узнать, получил ли я все сообщение?

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

  • SOCK_DGRAM: единственный вызов recv всегда возвращает одну дейтаграмму.

Есть ли способ сделать буфер не имеющим фиксированного пространства, так что я могу продолжать добавлять его, не опасаясь исчерпать пространство?

Нет. Однако вы можете попробовать изменить размер буфера с помощью realloc() (если он был первоначально выделен malloc() или calloc(), то есть).

Ответ 2

Для потоковых протоколов, таких как TCP, вы можете в значительной степени настроить буфер на любой размер. При этом рекомендуется использовать общие значения, которые имеют значения 2, например 4096 или 8192.

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

Да, вы можете продолжать наращивать свой буфер. Вы можете сделать recv в середине буфера, начиная со смещения idx, вы бы сделали:

recv(socket, recv_buffer + idx, recv_buffer_size - idx, 0);

Ответ 3

Если у вас есть сокет SOCK_STREAM, recv просто получает "до первых 3000 байт" из потока. Нет четких указаний относительно того, насколько велик буфер: единственный раз, когда вы знаете, насколько велик поток, - когда все это делается; -).

Если у вас есть сокет SOCK_DGRAM, а датаграмма больше, чем буфер, recv заполняет буфер первой частью дейтаграммы, возвращает -1 и устанавливает errno в EMSGSIZE. К сожалению, если протокол UDP, это означает, что остальная датаграмма потеряна - часть того, почему UDP называется ненадежным протоколом (я знаю, что существуют надежные протоколы дейтаграмм, но они не очень популярный - я не мог назвать его в семействе TCP/IP, несмотря на то, что он хорошо знал последнее: -).

Чтобы динамически вырастить буфер, сначала назначьте его malloc и используйте realloc по мере необходимости. Но это не поможет вам с recv из источника UDP, увы.

Ответ 4

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

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

#include <sys/ioctl.h>
int size;
ioctl(sockfd, FIONREAD, &size);

В качестве альтернативы вы можете использовать MSG_PEEK и MSG_TRUNC вызова recv() для получения ожидаемого размера дейтаграммы.

ssize_t size = recv(sockfd, buf, len, MSG_PEEK | MSG_TRUNC);

Вам нужно MSG_PEEK чтобы просмотреть (не получить) ожидающее сообщение - recv возвращает реальный, а не усеченный размер; и вам нужен MSG_TRUNC чтобы не переполнять текущий буфер.

Тогда вы можете просто использовать malloc(size) реального буфера и дейтаграмму recv().

Ответ 5

На ваш вопрос нет абсолютного ответа, потому что технология всегда должна зависеть от реализации. Я предполагаю, что вы общаетесь в UDP, потому что размер входящего буфера не создает проблем для связи TCP.

Согласно RFC 768 размер пакета (с учетом заголовка) для UDP может варьироваться от 8 до 65 515 байт. Таким образом, размер отказоустойчивого входящего буфера составляет 65 507 байт (~ 64 КБ).

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

Каков оптимальный размер пакета UDP для максимальной пропускной способности?
Какой самый большой размер безопасного UDP-пакета в Интернете

Ответ 6

16kb примерно прав; если вы используете гигабитный ethernet, каждый пакет может иметь размер 9 КБ.