Яким повинен бути буфер мого 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()відповіді на буфер?

Я знаю, що це багато питань в одному, але я дуже вдячний за будь-які відповіді.

Відповіді:


230

Відповіді на ці запитання різняться залежно від того, використовуєте ви потоковий socket ( SOCK_STREAM) або socket дейтаграми ( 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 виглядає так ((! (strstr (recv_buffer, "\ r \ n \ r \ n")); моє запитання: чи можливо для одного recv отримати "\ r \ n" і в наступний recv отримаємо "\ r \ n", щоб мій стан поки не справдився?
adhanlon

3
Так. Ви можете вирішити цю проблему, зациклившись, якщо у вас немає повного повідомлення, і вклавши байти з наступного recvв буфер після часткового повідомлення. Не слід використовувати strstr()в сирому буфері, заповненому recv()- немає гарантії, що він містить нуль-термінатор, тому це може спричинити strstr()збій.
caf

3
У випадку з UDP немає нічого поганого в тому, щоб надсилати пакети UDP вище 1400 байт. Фрагментація є цілком законною і основоположною частиною протоколу IP (навіть у IPv6, але завжди первинний відправник повинен виконувати фрагментацію). Для UDP ви завжди економите, якщо використовуєте буфер об’ємом 64 КБ, оскільки жоден IP-пакет (v4 або v6) не може бути розміром понад 64 КБ (навіть не фрагментований), і це включає навіть заголовки IIRC, тому дані завжди будуть нижче 64 КБ точно.
Мецькі

1
@caf чи потрібно спорожнювати буфер під час кожного виклику recv ()? Я бачив цикл коду, збираю ці дані та повторюйте його, щоб зібрати більше даних. Але якщо буфер коли-небудь заповнюється, вам не потрібно його спорожняти, щоб уникнути порушення пам'яті через написання, передайте об'єм пам'яті, виділений для буфера?
Alex_Nabu

1
@Alex_Nabu: Вам не потрібно спорожнювати його, доки в ньому залишиться простір, і ви не скажете recv()писати більше байтів, ніж залишилося місця.
caf

16

Для потокових протоколів, таких як TCP, ви можете майже встановити буфер будь-якого розміру. З огляду на це, рекомендуються загальні значення, які є повноваженнями 2, наприклад 4096 або 8192.

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

Так, ви можете продовжувати рости буфера. Ви можете зробити реверс в середину буфера, починаючи з зміщення idx, ви б зробили:

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

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 Кб абсолютно безпечно і гарантує, що ви ніколи не втрачаєте жодних даних під час повторної роботи на UDP-розетці.
Мецькі

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


MSG_PEEK | MSG_TRUNC не має сенсу.
Маркіз Лорн

3
Ви хочете, щоб MSG_PEEK заглянув (не отримав) повідомлення, що чекає, щоб отримати його розмір (recv повертає реальний, не усічений розмір), і вам потрібно MSG_TRUNC, щоб не переповнювати ваш поточний буфер. Як тільки ви отримаєте розмір, ви виділите правильний буфер і отримаєте (не зазирнути, не врізати) повідомлення очікування.
смокку

@ Алекс Мартеллі каже, що 64 КБ - це максимальний розмір пакету UDP, тому, якщо ми malloc()для буфера 64 КБ, то MSG_TRUNCце непотрібно?
mLstudent33

1
IP-протокол підтримує фрагментацію, тому дейтаграма може бути більшою, ніж один пакет - вона буде фрагментована і передається в декількох пакетах. Також SOCK_DGRAMне тільки UDP.
smokku

1

Немає абсолютної відповіді на ваше запитання, оскільки технологія завжди повинна залежати від впровадження. Я припускаю, що ви спілкуєтесь в UDP, оскільки розмір вхідного буфера не приносить проблем TCP-зв’язку.

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

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

Який оптимальний розмір пакету UDP для максимальної пропускної здатності?
Який найбільший розмір безпечного пакету UDP в Інтернеті


-4

16kb - приблизно справа; якщо ви використовуєте гігабітну мережу, кожен пакет може бути розміром 9 кбіт.


3
TCP-сокети - це потоки, це означає, що recv може повертати дані, накопичені з декількох пакетів, тому розмір пакета абсолютно не має значення для TCP. У випадку UDP кожен виклик recv повертає максимум один пакет UDP, тут розмір пакета є релевантним, але правильний розмір пакета становить близько 64 КБ, оскільки пакет UDP може (і часто буде) фрагментований, якщо потрібно. Однак жоден IP-пакет не може перевищувати 64 КБ, навіть з фрагментацією, таким чином реквізиція на UDP-сокет може максимум повернути 64 КБ (а те, що не повернуто, відкидається для поточного пакету!)
Меккі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.