При привязке клиентского TCP-сокета к конкретному локальному порту с Winsock SO_REUSEADDR не имеет никакого эффекта

Я привязываю клиентский TCP-сокет к определенному локальному порту. Чтобы справиться с ситуацией, когда сокет остается в TIME_WAIT течение некоторого времени, я использую setsockopt() с SO_REUSEADDR в сокете.

Он работает в Linux, но не работает в Windows, я получаю WSAEADDRINUSE при connect() когда предыдущее соединение все еще находится в TIME_WAIT .

MSDN не совсем ясно, что должно произойти с клиентскими сокетами:

[…] Для серверных приложений, которым необходимо привязать несколько сокетов к одному и тому же номеру порта, рассмотрите возможность использования setsockopt ( SO_REUSEADDR ). Клиентским приложениям, как правило, не требуется связывание со связью на all-connect, автоматически выбирает неиспользуемый порт. […]

Как мне избежать этого?

Когда вы создаете сокет с socket() , он имеет только тип и семейство протоколов. Идеал – bind() его к локальному адресу: порт тоже.

Указанная вами ошибка обычно происходит, когда последнее соединение с одним и тем же хостом: порт не имел изящного отключения (FIN / ACK FIN / ACK). В этих случаях гнездо остается в TIME_WAIT в течение определенного периода времени (зависит от ОС, но настраивается).

Что происходит тогда, когда вы пытаетесь connect() к одному и тому же хосту и тому же порту, он использует имя / адрес / порт сокета по умолчанию / etc, но эта комбинация уже используется вашим гнездом для зомби . Чтобы этого избежать, вы можете изменить локальный адрес: порт, используемый для установления соединения, вызывая bind() после создания сокета, предоставляя структуру sockaddr заполненную вашим локальным адресом и случайным портом.

 int main() { int ret, fd; struct sockaddr_in sa_dst; struct sockaddr_in sa_loc; char buffer[1024] = "GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n"; fd = socket(AF_INET, SOCK_STREAM, 0); // Local memset(&sa_loc, 0, sizeof(struct sockaddr_in)); sa_loc.sin_family = AF_INET; sa_loc.sin_port = htons(LOCAL_RANDOM_PORT); sa_loc.sin_addr.s_addr = inet_addr(LOCAL_IP_ADDRESS); ret = bind(fd, (struct sockaddr *)&sa_loc, sizeof(struct sockaddr)); assert(ret != -1); // Remote memset(&sa_dst, 0, sizeof(struct sockaddr_in)); sa_dst.sin_family = AF_INET; sa_dst.sin_port = htons(80); sa_dst.sin_addr.s_addr = inet_addr("64.233.163.104"); // google :) ret = connect(fd, (struct sockaddr *)&sa_dst, sizeof(struct sockaddr)); assert(ret != -1); send(fd, buffer, strlen(buffer), 0); recv(fd, buffer, sizeof(buffer), 0); printf("%s\r\n", buffer); } 

ОБНОВЛЕНИЕ : Поскольку использование определенного локального порта является требованием, рассмотрите настройку SO_LINGER с l_onoff=1 и l_linger=0 чтобы ваш сокет не блокировался при close / closesocket , он просто игнорирует данные в очереди и (надеюсь) закрывает fd. В крайнем случае вы можете настроить задержку TIME_WAIT , изменив значение этого раздела реестра (сильно обескураженный!):

 HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay 

Вы не указали, на какой платформе Windows вы работаете, что может повлиять на вещи, а также на того, с каким руководителем безопасности вы работаете (т. Е. Вы администратор?) …

Это может помочь: http://blogs.msdn.com/wndp/archive/2005/08/03/Anthony-Jones.aspx

Будьте осторожны при привязке локального порта НЕ использовать loopback-адрес «127.0.0.1», или вы получите таймауты подключения. Лучше не заполнять файл sa_loc.sin_addr.s_addr вообще – это работает отлично.