При привязке клиентского TCP-сокета к определенному локальному порту с помощью Winsock SO_REUSEADDR не оказывает никакого влияния - PullRequest
5 голосов
/ 09 апреля 2010

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

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

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

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

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

Ответы [ 3 ]

10 голосов
/ 09 апреля 2010

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

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

Что происходит тогда, когда вы пытаетесь connect() к тому же хосту и тому же порту, он использует имя сокета по умолчанию / адрес / порт / и т.д., но эта комбинация уже используется вашим зомби разъем. Чтобы избежать этого, вы можете изменить локальный адрес: порт, используемый для установления соединения, вызвав 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
0 голосов
/ 26 июля 2013

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

0 голосов
/ 09 апреля 2010

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

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

...