¿Qué tan grande debe ser mi búfer recv al llamar a recv en la biblioteca de sockets?

129

Tengo algunas preguntas sobre la biblioteca de sockets en C. Aquí hay un fragmento de código al que me referiré en mis preguntas.

char recv_buffer[3000];
recv(socket, recv_buffer, 3000, 0);
  1. ¿Cómo decido qué tan grande hacer recv_buffer? Estoy usando 3000, pero es arbitrario.
  2. ¿Qué sucede si recv()recibo un paquete más grande que mi búfer?
  3. ¿Cómo puedo saber si he recibido el mensaje completo sin volver a llamar a recv y esperarlo para siempre cuando no hay nada que recibir?
  4. ¿Hay alguna manera de hacer que un búfer no tenga una cantidad fija de espacio, de modo que pueda seguir agregándolo sin temor a quedarse sin espacio? tal vez usando strcatpara concatenar la última recv()respuesta al búfer?

Sé que son muchas preguntas en una, pero agradecería mucho cualquier respuesta.

adhanlon
fuente

Respuestas:

230

Las respuestas a estas preguntas varían dependiendo de si está utilizando un socket de flujo ( SOCK_STREAM) o un socket de datagrama ( SOCK_DGRAM): dentro de TCP / IP, el primero corresponde a TCP y el segundo a UDP.

¿Cómo sabes qué tan grande hacer pasar el búfer recv()?

  • SOCK_STREAM: Realmente no importa demasiado. Si su protocolo es transaccional / interactivo, simplemente elija un tamaño que pueda contener el mensaje / comando individual más grande que razonablemente esperaría (3000 probablemente esté bien). Si su protocolo está transfiriendo datos masivos, los búferes más grandes pueden ser más eficientes: una buena regla general es casi la misma que el tamaño del búfer de recepción del núcleo del zócalo (a menudo alrededor de 256kB).

  • SOCK_DGRAM: Utilice un búfer lo suficientemente grande como para contener el paquete más grande que envíe su protocolo de nivel de aplicación. Si está utilizando UDP, entonces, en general, su protocolo de nivel de aplicación no debería enviar paquetes de más de 1400 bytes, ya que seguramente deberán fragmentarse y volverse a ensamblar.

¿Qué sucede si recvun paquete es más grande que el búfer?

  • SOCK_STREAM: La pregunta realmente no tiene sentido, ya que los sockets de flujo no tienen un concepto de paquetes, son solo un flujo continuo de bytes. Si hay más bytes disponibles para leer de los que tiene espacio para el búfer, el sistema operativo los pondrá en cola y estarán disponibles para su próxima llamada recv.

  • SOCK_DGRAM: Los bytes sobrantes se descartan.

¿Cómo puedo saber si he recibido el mensaje completo?

  • SOCK_STREAM: Debe crear alguna forma de determinar el final del mensaje en su protocolo de nivel de aplicación. Por lo general, se trata de un prefijo de longitud (que comienza cada mensaje con la longitud del mensaje) o un delimitador de fin de mensaje (que podría ser una nueva línea en un protocolo basado en texto, por ejemplo). Una tercera opción, menos utilizada, es ordenar un tamaño fijo para cada mensaje. También son posibles combinaciones de estas opciones, por ejemplo, un encabezado de tamaño fijo que incluye un valor de longitud.

  • SOCK_DGRAM: Una sola recvllamada siempre devuelve un solo datagrama.

¿Hay alguna manera de hacer que un búfer no tenga una cantidad fija de espacio, de modo que pueda seguir agregándolo sin temor a quedarse sin espacio?

No. Sin embargo, puede intentar cambiar el tamaño del búfer utilizando realloc()(si se asignó originalmente con malloc()o calloc(), eso es).

coste y flete
fuente
1
Tengo un "/ r / n / r / n" al final de un mensaje en el protocolo que estoy usando. Y tengo un ciclo do while, dentro de lo que estoy llamando recv coloco el mensaje al comienzo de recv_buffer. y mi declaración while se ve así mientras while ((! (strstr (recv_buffer, "\ r \ n \ r \ n")); Mi pregunta es, ¿es posible que una recv obtenga "\ r \ n" y en el siguiente recv obtener "\ r \ n", para que mi condición while nunca se haga realidad
Adhanlon
3
Sí lo es. Puede resolver ese problema dando vueltas si no tiene un mensaje completo y rellenando los bytes del siguiente recven el búfer después del mensaje parcial. No debe usar strstr()en el búfer sin procesar lleno recv(); no hay garantía de que contenga un terminador nulo, por lo que podría provocar strstr()un bloqueo.
caf
3
En el caso de UDP, no hay nada de malo en enviar paquetes UDP de más de 1400 bytes. La fragmentación es perfectamente legal y una parte fundamental del protocolo IP (incluso en IPv6, aunque siempre el remitente inicial debe realizar la fragmentación). Para UDP, siempre se guarda si usa un búfer de 64 KB, ya que ningún paquete IP (v4 o v6) puede tener un tamaño superior a 64 KB (ni siquiera cuando está fragmentado) y esto incluso incluye los encabezados IIRC, por lo que los datos siempre serán debajo de 64 KB seguro.
Mecki
1
@caf, ¿necesita vaciar el búfer en cada llamada a recv ()? He visto bucles de código y recopilo los datos y los repito nuevamente, lo que debería recopilar más datos. Pero si el búfer alguna vez se llena, ¿no necesita vaciarlo para evitar una violación de memoria debido a que la escritura pasa la cantidad de memoria asignada para el búfer?
Alex_Nabu
1
@Alex_Nabu: No es necesario vaciarlo siempre que quede algo de espacio y no le pida recv()que escriba más bytes de los que quedan.
caf
16

Para protocolos de transmisión como TCP, puede configurar su búfer en cualquier tamaño. Dicho esto, se recomiendan valores comunes que son potencias de 2, como 4096 u 8192.

Si hay más datos, entonces cuál es su búfer, simplemente se guardará en el núcleo para su próxima llamada recv.

Sí, puedes seguir haciendo crecer tu búfer. Puede hacer una recepción en el medio del búfer comenzando en el desplazamiento idx, haría:

recv(socket, recv_buffer + idx, recv_buffer_size - idx, 0);
R Samuel Klatchko
fuente
66
El poder de dos puede ser más eficiente de múltiples maneras, y se recomienda encarecidamente.
Yann Ramin
3
Al desarrollar @theatrus, una eficiencia notable es que el operador de módulo se puede reemplazar por bit a bit y con una máscara (por ejemplo, x% 1024 == x & 1023), y la división de enteros se puede reemplazar por una operación de desplazamiento a la derecha (por ejemplo, x / 1024 = = x / 2 ^ 10 == x >> 10)
vicatcu
15

Si tienes un SOCK_STREAMenchufe,recv solo obtiene "hasta los primeros 3000 bytes" de la transmisión. No hay una guía clara sobre qué tan grande hacer el búfer: la única vez que sabes qué tan grande es una transmisión, es cuando todo está hecho ;-).

Si tiene un SOCK_DGRAMsocket y el datagrama es más grande que el búfer, recvllena el búfer con la primera parte del datagrama, devuelve -1 y establece errno en EMSGSIZE. Desafortunadamente, si el protocolo es UDP, esto significa que el resto del datagrama se pierde, parte de por qué UDP se llama no confiable protocolo (sé que hay protocolos de datagramas confiables pero no son muy populares; no pude nombrar uno en la familia TCP / IP, a pesar de conocer a este último bastante bien ;-).

Para hacer crecer un búfer de forma dinámica, asígnelo inicialmente mallocy úselo reallocsegún sea necesario. Pero eso no lo ayudará recvdesde una fuente UDP, por desgracia.

Alex Martelli
fuente
77
Como UDP siempre devuelve como máximo un paquete UDP (incluso si hay varios en el búfer de socket) y ningún paquete UDP puede estar por encima de 64 KB (un paquete IP puede tener como máximo 64 KB, incluso cuando está fragmentado), usar un búfer de 64 KB es absolutamente seguro y garantiza que nunca perderá ningún dato durante una recepción en un socket UDP.
Mecki
7

Para el SOCK_STREAMzócalo, el tamaño del búfer realmente no importa, porque solo está extrayendo algunos de los bytes en espera y puede recuperar más en una próxima llamada. Simplemente elija el tamaño de búfer que pueda pagar.

Para el SOCK_DGRAMzócalo, obtendrá la parte adecuada del mensaje de espera y el resto se descartará. Puede obtener el tamaño del datagrama de espera con el siguiente ioctl:

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

Alternativamente, puede usar los indicadores MSG_PEEKy MSG_TRUNCde la recv()llamada para obtener el tamaño del datagrama en espera.

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

Es necesario MSG_PEEKque peek (no recibir) el mensaje en espera - regresa recv lo real, y no truncada tamaño; y no necesita MSG_TRUNCdesbordar su búfer actual.

Entonces puede simplemente malloc(size)el búfer real y el recv()datagrama.

smokku
fuente
MSG_PEEK | MSG_TRUNC no tiene sentido.
Marqués de Lorne el
3
Desea que MSG_PEEK mire (no reciba) el mensaje de espera, para obtener su tamaño (recv devuelve el tamaño real, no truncado) y necesita MSG_TRUNC para no desbordar su búfer actual. Una vez que obtiene el tamaño, asigna el búfer correcto y recibe (no mira, no trunca) el mensaje de espera.
smokku
@Alex Martelli dice que 64 KB es el tamaño máximo de un paquete UDP, por lo que si necesitamos malloc()un búfer de 64 KB, ¿ MSG_TRUNCes innecesario?
mLstudent33
1
El protocolo IP admite la fragmentación, por lo que el datagrama puede ser más grande que un solo paquete; se fragmentará y transmitirá en múltiples paquetes. Además SOCK_DGRAMno es solo UDP.
smokku
1

No hay una respuesta absoluta a su pregunta, porque la tecnología siempre está destinada a ser específica de la implementación. Supongo que se está comunicando en UDP porque el tamaño del búfer entrante no trae problemas a la comunicación TCP.

De acuerdo con RFC 768 , el tamaño del paquete (incluido el encabezado) para UDP puede variar de 8 a 65 515 bytes. Por lo tanto, el tamaño a prueba de fallas para el búfer entrante es 65 507 bytes (~ 64 KB)

Sin embargo, no todos los paquetes grandes pueden ser enrutados adecuadamente por dispositivos de red, consulte la discusión existente para obtener más información:

¿Cuál es el tamaño óptimo de un paquete UDP para un rendimiento máximo?
¿Cuál es el tamaño de paquete UDP seguro más grande en Internet?

YeenFei
fuente
-4

16kb es lo correcto; Si está utilizando Gigabit Ethernet, cada paquete puede tener un tamaño de 9 kb.

Andrew McGregor
fuente
3
Los sockets TCP son flujos, lo que significa que una recepción puede devolver datos acumulados de múltiples paquetes, por lo que el tamaño del paquete es totalmente irrelevante para TCP. En el caso de UDP, cada llamada de recepción devuelve como máximo un único paquete UDP, aquí el tamaño del paquete es relevante pero el tamaño correcto del paquete es de aproximadamente 64 KB, ya que un paquete UDP puede (y a menudo se fragmentará si es necesario). Sin embargo, ningún paquete IP puede estar por encima de 64 KB, ni siquiera con fragmentación, por lo tanto, la recepción en un socket UDP puede devolver como máximo 64 KB (¡y lo que no se devuelve se descarta para el paquete actual!)
Mecki