por - socket servidor c++
¿Cuál es el tamaño de un buffer de envío de socket en Windows? (3)
Según mi entendimiento, cada socket está asociado con dos búferes, un búfer de envío y un búfer de recepción, así que cuando llamo a la función send()
, lo que sucede es que los datos que se enviarán se colocarán en el búfer de envío, y es La responsabilidad de Windows ahora es enviar el contenido de este búfer de envío al otro extremo.
En un socket de bloqueo, la función send()
no vuelve hasta que todos los datos que se le suministran se hayan colocado en el búfer de envío.
Entonces, ¿cuál es el tamaño del búfer de envío?
Realicé la siguiente prueba (enviando 1 GB de datos):
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#include <Windows.h>
int main()
{
// Initialize Winsock
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
// Create socket
SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
//----------------------
// Connect to 192.168.1.7:12345
sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr("192.168.1.7");
address.sin_port = htons(12345);
connect(s, (sockaddr*)&address, sizeof(address));
//----------------------
// Create 1 GB buffer ("AAAAAA...A")
char *buffer = new char[1073741824];
memset(buffer, 0x41, 1073741824);
// Send buffer
int i = send(s, buffer, 1073741824, 0);
printf("send() has returned/nReturn value: %d/nWSAGetLastError(): %d/n", i, WSAGetLastError());
//----------------------
getchar();
return 0;
}
Salida:
send() has returned
Return value: 1073741824
WSAGetLastError(): 0
send()
ha regresado de inmediato, ¿esto significa que el búfer de envío tiene un tamaño de al menos 1 GB?
Esta es alguna información sobre la prueba:
- Estoy usando un socket de bloqueo TCP.
- Me he conectado a una máquina LAN.
- Versión de cliente de Windows: Windows 7 Ultimate 64-bit.
- Versión de servidor de Windows: Windows XP SP2 de 32 bits (instalado en Virtual Box).
Edición: también he intentado conectarme a Google (173.194.116.18:80) y obtuve los mismos resultados.
Edición 2: he descubierto algo extraño, establecer el búfer de envío en un valor entre 64 KB y 130 KB hará que send()
funcione como se esperaba.
int send_buffer = 64 * 1024; // 64 KB
int send_buffer_sizeof = sizeof(int);
setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char*)send_buffer, send_buffer_sizeof);
Edición 3: Resultó (gracias a Harry Johnston) que he usado setsockopt()
de una manera incorrecta, así es como se usa:
setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char*)&send_buffer, send_buffer_sizeof);
Establecer el búfer de envío en un valor entre 64 KB y 130 KB no hace que send()
funcione como se esperaba, sino que establecer el búfer de envío en 0
hace que se bloquee (esto es lo que noté de todos modos, no tengo ninguna documentación para este comportamiento).
Entonces, mi pregunta ahora es: ¿dónde puedo encontrar una documentación sobre cómo funciona send()
(y quizás otras operaciones de socket) en Windows?
En un socket de bloqueo, la función send () no vuelve hasta que todos los datos que se le suministran se hayan colocado en el búfer de envío.
Eso no está garantizado. Si hay espacio disponible en el búfer, pero no hay suficiente espacio para toda la información, el socket puede (y generalmente aceptará) cualquier información que pueda e ignorar el resto. El valor de retorno de send()
le dice cuántos bytes fueron realmente aceptados. Tienes que llamar a send()
nuevamente para enviar los datos restantes.
Entonces, ¿cuál es el tamaño del búfer de envío?
Use getsockopt()
con la opción SO_SNDBUF
para averiguarlo.
Use setsockopt()
con la opción SO_SNDBUF
para especificar su propio tamaño de búfer. Sin embargo, el socket puede imponer un límite máximo en el valor que especifique. Use getsockopt()
para averiguar qué tamaño se asignó realmente.
Después de investigar sobre este tema. Esto es lo que creo que es la respuesta correcta:
Al llamar a send()
, hay dos cosas que podrían suceder:
Si hay datos pendientes que están por debajo de
SO_SNDBUF
, entoncessend()
devolverá inmediatamente (y no importa si está enviando 5 KB o está enviando 500 MB).Si hay datos pendientes que están por encima o iguales a
SO_SNDBUF
, entoncessend()
se bloqueará hasta que se hayan enviado suficientes datos para restaurar los datos pendientes por debajo deSO_SNDBUF
.
Tenga en cuenta que este comportamiento solo se aplica a los sockets de Windows y no a los sockets POSIX. Creo que los zócalos POSIX solo usan un búfer de envío de tamaño fijo (corríjame si me equivoco).
Ahora volvamos a la pregunta principal "¿Cuál es el tamaño de un búfer de envío de socket en Windows?". Supongo que si tienes suficiente memoria, podría crecer más allá de 1 GB si es necesario (aunque no estoy seguro de cuál es el límite máximo).
Puedo reproducir este comportamiento, y utilizando el Monitor de recursos es fácil ver que Windows asigna 1 GB de espacio en el búfer cuando se produce el envío ().
Una característica interesante es que si realiza un segundo envío inmediatamente después del primero, esa llamada no regresa hasta que ambos envíos se hayan completado. El espacio de búfer del primer envío se libera una vez que el envío se ha completado, pero el segundo send () continúa bloqueando hasta que todos los datos se hayan transferido.
Sospecho que la diferencia en el comportamiento se debe a que la segunda llamada a send () ya estaba bloqueando cuando se completó el primer envío. La tercera llamada a send () devuelve inmediatamente (y se asigna 1 GB de espacio de almacenamiento) al igual que lo hizo la primera, y así sucesivamente, alternando.
Así que concluyo que la respuesta a la pregunta ("¿qué tan grandes son los buffers de envío?") Es "tan grande como Windows considere oportuno". El resultado final es que, para evitar agotar la memoria del sistema, probablemente deba restringir los envíos bloqueados a no más de unos pocos cientos de megabytes.
Su llamada a setsockopt () es incorrecta; se supone que el cuarto argumento es un puntero a un entero, no un entero convertido a un puntero. Una vez que se corrige esto, resulta que establecer el tamaño del búfer en cero hace que el envío () siempre se bloquee.
Para resumir, el comportamiento observado es que send () regresará inmediatamente siempre que:
- Hay suficiente memoria para almacenar todos los datos proporcionados.
- no hay un envío ya en curso
- el tamaño del búfer no se establece en cero
De lo contrario, volverá una vez que los datos hayan sido enviados.
KB214397 describe algo de esto - ¡gracias Hans! En particular, describe que establecer el tamaño del búfer en cero desactiva el búfer de Winsock y comenta que "Si es necesario, Winsock puede almacenar un búfer significativamente mayor que el tamaño del búfer SO_SNDBUF".
(La notificación de finalización descrita no coincide exactamente con el comportamiento observado, dependiendo de cómo se interpreta "el envío previamente almacenado en búfer". Pero está cerca).
Tenga en cuenta que, aparte del riesgo de agotar inadvertidamente la memoria del sistema, nada de esto debería importar. Si realmente necesita saber si el código en el otro extremo ya ha recibido todos sus datos, la única forma confiable de hacerlo es hacer que se lo comuniquen.