Каков размер буфера отправки сокетов в Windows?
Основываясь на моем понимании, каждый сокет связан с двумя буферами, буфером отправки и буфером приема, поэтому, когда я вызываю функцию send()
, происходит то, что отправляемые данные будут помещены в буфер отправки, и теперь Windows обязан отправлять содержимое этого буфера отправки на другой конец.
В блокирующем сокете функция send()
не возвращается, пока все данные, переданные ей, не будут помещены в буфер отправки.
Итак, каков размер буфера отправки?
Я выполнил следующий тест (отправка данных объемом 1 ГБ):
#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;
}
Вывод:
send() has returned
Return value: 1073741824
WSAGetLastError(): 0
send()
немедленно возвращается, означает ли это, что буфер отправки имеет размер не менее 1 ГБ?
Это информация о тесте:
- Я использую блокировку TCP-блокировки.
- Я подключился к LAN-машине.
- Клиентская версия Windows: 64-разрядная версия Windows 7 Ultimate.
- Сервер Windows версия: Windows XP SP2 32-разрядная (установлена в Virtual Box).
Изменить: Я также попытался подключиться к Google (173.194.116.18:80), и я получил те же результаты.
Изменить 2: Я обнаружил что-то странное, установив буфер отправки на значение от 64 КБ до 130 КБ, сделает send()
работать как ожидалось!
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);
Изменить 3: Оказалось (спасибо Гарри Джонстону), что я неправильно использовал setsockopt()
, вот как он используется:
setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char*)&send_buffer, send_buffer_sizeof);
Настройка буфера отправки на значение от 64 КБ до 130 КБ не делает send()
работать как ожидалось, , а скорее устанавливает буфер отправки в 0
блокирует (это то, что я заметил в любом случае, у меня нет документации для этого поведения).
Итак, теперь мой вопрос: где я могу найти документацию о том, как send()
(и, возможно, другие операции сокета) работают под Windows?
Ответы
Ответ 1
После изучения этого вопроса. Вот что я считаю правильным ответом:
При вызове send()
возможны две вещи:
-
Если есть ожидающие данные ниже SO_SNDBUF
, то send()
будет немедленно возвращаться (и неважно, отправляете ли вы 5 КБ или отправляете 500 МБ).
-
Если есть ожидающие данные выше или равные SO_SNDBUF
, то send()
будет блокироваться до тех пор, пока не будет отправлено достаточно данных для восстановления ожидающих данных ниже SO_SNDBUF
.
Обратите внимание, что это поведение применимо только к сокетам Windows, а не к сокетам POSIX. Я думаю, что в сокетах POSIX используется только один буфер отправки фиксированного размера (исправьте меня, если я ошибаюсь).
Теперь вернемся к вашему основному вопросу "Каков размер буфера отправки сокетов в Windows?". Я думаю, если у вас будет достаточно памяти, он может вырасти до 1 ГБ, если это необходимо (не уверен, что такое максимальный предел).
Ответ 2
Я могу воспроизвести это поведение, и с помощью Resource Monitor легко увидеть, что Windows действительно выделяет 1 ГБ пространства для буфера при возникновении send().
Интересной особенностью является то, что если вы выполняете вторую посылку сразу после первой, этот вызов не возвращается, пока оба отправления не будут завершены. Буферное пространство от первой отправки освобождается после завершения отправки, но вторая функция send() продолжает блокироваться до тех пор, пока все данные не будут переданы.
Я подозреваю, что разница в поведении связана с тем, что второй вызов send() уже блокировался, когда первая отправка была завершена. Третий вызов send() немедленно возвращается (и выделено 1 ГБ пространства в буфере) так же, как и первый, и т.д. Чередуется.
Итак, я пришел к выводу, что ответ на вопрос ( "насколько большие буферы отправки?" ) является "размером с Windows, который подходит". Таким образом, чтобы избежать исчерпания системной памяти, вы должны, вероятно, ограничить блокировку отправки не более чем несколькими сотнями мегабайт.
Ваш вызов setsockopt() неверен; четвертый аргумент должен быть указателем на целое число, а не целое число, преобразованное в указатель. Как только это исправлено, выясняется, что установка размера буфера в ноль приводит к тому, что send() всегда блокируется.
Подводя итог, наблюдаемое поведение заключается в том, что send() немедленно вернется:
- достаточно памяти для хранения всех предоставленных данных
- уже не идет передача
- размер буфера не равен нулю
В противном случае он вернется после отправки данных.
KB214397 описывает некоторые из них - спасибо Хансу! В частности, он описывает, что установка размера буфера в ноль отключает буферизацию Winsock и комментарии, которые "При необходимости Winsock может буферизовать значительно больше, чем размер буфера SO_SNDBUF".
(Описанное уведомление о завершении не совсем соответствует наблюдаемому поведению, в зависимости от того, как вы интерпретируете "ранее буферизованное послание", но оно закрывается.)
Обратите внимание, что, помимо риска непреднамеренного исчерпания системной памяти, ничто из этого не имеет значения. Если вам действительно нужно знать, получил ли код на другом конце все ваши данные, единственный надежный способ сделать это - заставить его сказать вам.
Ответ 3
В блокирующем сокете функция send() не возвращается, пока все данные, переданные ей, не будут помещены в буфер отправки.
Это не гарантируется. Если имеется свободное пространство для буфера, но недостаточно места для всех данных, сокет может (и обычно будет) принимать любые данные, которые он может, и игнорировать остальные. Возвращаемое значение send()
сообщает вам, сколько байтов было фактически принято. Вы должны снова набрать send()
, чтобы отправить оставшиеся данные.
Итак, каков размер буфера отправки?
Используйте getsockopt()
с опцией SO_SNDBUF
, чтобы узнать.
Используйте setsockopt()
с опцией SO_SNDBUF
, чтобы указать свой собственный размер буфера. Тем не менее, гнездо может наложить максимальную максимальную величину на указанное вами значение. Используйте getsockopt()
, чтобы узнать, какой размер был фактически назначен.