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

129

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

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

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

adhanlon
источник

Ответы:

230

Ответы на эти вопросы различаются в зависимости от того, используете ли вы сокет потока ( SOCK_STREAM) или сокет дейтаграммы ( 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(), то есть).

кафе
источник
1
У меня есть "/ r / n / r / n" в конце сообщения в протоколе, который я использую. И у меня есть цикл do while, внутри я вызываю recv и помещаю сообщение в начало recv_buffer. и мой оператор while выглядит так: while ((! (strstr (recv_buffer, "\ r \ n \ r \ n")); Мой вопрос: возможно ли для одного recv получить "\ r \ n" и в next recv получить "\ r \ n", чтобы мое условие while никогда не
выполнялось
3
Да, это так. Вы можете решить эту проблему, перейдя в цикл, если у вас нет полного сообщения, и вставив байты из следующего recvв буфер после частичного сообщения. Вы не должны использовать strstr()его для необработанного буфера, заполненного recv()- нет гарантии, что он содержит нулевой терминатор, поэтому это может вызвать strstr()сбой.
кафе
3
В случае UDP нет ничего плохого в отправке пакетов UDP размером более 1400 байт. Фрагментация совершенно законна и является фундаментальной частью протокола IP (даже в IPv6, но всегда исходный отправитель должен выполнять фрагментацию). Для UDP вы всегда экономите, если используете буфер размером 64 КБ, поскольку размер IP-пакета (v4 или v6) не может превышать 64 КБ (даже если он фрагментирован), и это даже включает заголовки IIRC, поэтому данные всегда будут ниже 64 КБ точно.
Mecki
1
@caf вам нужно очищать буфер при каждом вызове recv ()? Я видел цикл кода, собираю данные и снова зацикливаю, что должно собрать больше данных. Но если буфер когда-либо заполняется, разве вам не нужно его очищать, чтобы избежать нарушения памяти из-за записи прохода объема памяти, выделенного для буфера?
Alex_Nabu 01
1
@Alex_Nabu: вам не нужно его очищать, пока в нем остается немного места, и вы не говорите, что recv()нужно записать больше байтов, чем осталось места.
caf
16

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

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

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

recv(socket, recv_buffer + idx, recv_buffer_size - idx, 0);
R Самуэль Клатчко
источник
6
Степень двойки может быть более эффективной во многих отношениях, и это настоятельно рекомендуется.
Ян Рамин
3
развивая @theatrus, заметная эффективность заключается в том, что оператор по модулю может быть заменен побитовым и маской (например, x% 1024 == x & 1023), а целочисленное деление может быть заменено операцией сдвига вправо (например, x / 1024 = = x / 2 ^ 10 == x >> 10)
vicatcu
15

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

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

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

Алекс Мартелли
источник
7
Поскольку UDP всегда возвращает не более одного пакета UDP (даже если в буфере сокета их несколько), и ни один пакет UDP не может превышать 64 КБ (IP-пакет может быть не более 64 КБ, даже если он фрагментирован), использование буфера 64 КБ абсолютно безопасен и гарантирует, что вы никогда не потеряете никаких данных во время recv на сокете UDP.
Mecki
7

Для 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()дейтаграмму.

smokku
источник
MSG_PEEK | MSG_TRUNC не имеет смысла.
Marquis of Lorne
3
Вы хотите, чтобы MSG_PEEK просматривал (не принимал) ожидающее сообщение, чтобы получить его размер (recv возвращает реальный, а не усеченный размер), и вам нужен MSG_TRUNC, чтобы не переполнять ваш текущий буфер. Как только вы получите размер, вы выделяете правильный буфер и получаете (не просматривая, не усекая) ожидающее сообщение.
smokku
@Alex Martelli говорит, что 64 КБ - это максимальный размер UDP-пакета, поэтому, если мы malloc()используем буфер размером 64 КБ, тогда MSG_TRUNCэто не нужно?
mLstudent33
1
Протокол IP поддерживает фрагментацию, поэтому дейтаграмма может быть больше одного пакета - она ​​будет фрагментирована и передана несколькими пакетами. И SOCK_DGRAMесть не только UDP.
смокку
1

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

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

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

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

YeenFei
источник
-4

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

Эндрю МакГрегор
источник
3
Сокеты TCP - это потоки, это означает, что recv может возвращать данные, накопленные из нескольких пакетов, поэтому размер пакета для TCP совершенно не имеет значения. В случае UDP каждый вызов recv возвращает не более одного пакета UDP, здесь размер пакета имеет значение, но правильный размер пакета составляет около 64 КБ, поскольку пакет UDP может (и часто будет) при необходимости фрагментирован. Однако ни один IP-пакет не может быть больше 64 КБ, даже при фрагментации, таким образом, recv на сокете UDP может возвращать не более 64 КБ (а то, что не возвращается, отбрасывается для текущего пакета!)
Mecki