Подтвердить что ты не робот

Зачем подключаться() давать EADDRNOTAVAIL?

У меня в моем приложении произошел сбой, который не кажется воспроизводимым. У меня возникло соединение сокетов TCP, и приложение попыталось его повторно подключить. Во втором вызове connect(), пытающемся повторно подключиться, я получил результат ошибки с errno == EADDRNOTAVAIL, который указывает страница man для connect(): "Указанный адрес недоступен на локальной машине".

Глядя на вызов connect(), второй аргумент представляет собой адрес, к которому относится ошибка, но, как я понимаю, этот аргумент является адресом сокета TCP удаленного хоста, поэтому я смущен о странице руководства, ссылающейся на локальную машину. Является ли этот адрес удаленным узлом сокета TCP недоступным с моей локальной машины? Если да, то почему? Это должно было быть вызвано connect() в первый раз до того, как соединение завершилось неудачно, и он попытался повторно подключиться и получил эту ошибку. Аргументы для connect() были одинаковыми оба раза.

Будет ли эта ошибка временной, которая, если бы я попыталась позвонить, снова могла бы уйти, если бы я подождал достаточно долго? Если нет, как я должен попытаться восстановиться после этого отказа?

4b9b3361

Ответ 1

Отметьте эту ссылку

http://www.toptip.ca/2010/02/linux-eaddrnotavail-address-not.html

РЕДАКТИРОВАТЬ: Да, я хотел добавить больше, но мне пришлось его разрезать из-за чрезвычайной ситуации

Вы закрыли сокет, прежде чем пытаться снова подключиться? Закрытие сообщит системе, что сокет (ip/port) теперь свободен.

Ниже перечислены дополнительные пункты:

  • Если локальный порт уже подключен к данному удаленному IP-порту и порту (т.е. уже есть идентичный socketpair), вы получите эту ошибку (см. ссылку ниже).
  • Привязка адреса сокета, который не является локальным, приведет к этой ошибке. если IP-адреса машины 127.0.0.1 и 1.2.3.4, и вы пытаетесь привязать к 1.2.3.5, вы получите эту ошибку.
  • EADDRNOTAVAIL: указанный адрес недоступен на удаленном компьютере, или поле адреса структуры имен - все нули.

Ссылка с ошибкой, похожей на вашу (ответ близок к нижней)

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4294599

Кажется, что ваш сокет в основном застрял в одном из внутренних состояний TCP и что добавление задержки для повторного подключения может решить вашу проблему, поскольку они, похоже, сделали это сообщение об ошибке.

Ответ 2

Это также может произойти, если указан недопустимый порт, например 0.

Ответ 3

Если вы не хотите изменять количество доступных временных портов (как предложено Дэвидом), или вам нужно больше соединений, чем теоретический максимум, есть два других способа уменьшить количество используемых портов. Тем не менее, они в разной степени нарушают стандарт TCP, поэтому их следует использовать с осторожностью.

Во-первых, включить SO_LINGER с нулевым секундовым таймаутом, заставляя стек TCP отправить пакет RST и сбросить состояние соединения. Однако есть одна тонкость: вы должны вызвать shutdown в дескрипторе файла сокета до close, чтобы у вас была возможность отправить пакет FIN перед пакетом RST. Таким образом, код будет выглядеть примерно так:

shutdown(fd, SHUT_RDWR);
struct linger linger;
linger.l_onoff = 1;
linger.l_linger = 0;
// todo: test for error
setsockopt(fd, SOL_SOCKET, SO_LINGER,
           (char *) &linger, sizeof(linger));
close(fd);

Сервер должен видеть только преждевременное соединение reset, если пакет FIN получает переупорядочивание с пакетом RST.

См. Параметр TCP SO_LINGER (ноль) - если требуется для получения более подробной информации. (По опыту, кажется, не имеет значения, где вы устанавливаете setsockopt.)

Во-вторых, используйте SO_REUSEADDR и явный bind (даже если вы клиент), что позволит Linux повторно использовать временные порты при запуске, прежде чем они будут выполнены. Обратите внимание, что вы должны использовать bind с INADDR_ANY и портом 0, иначе SO_REUSEADDR не соблюдается. Ваш код будет выглядеть примерно так:

int opts = 1;
// todo: test for error
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
         (char *) &opts, sizeof(int));

struct sockaddr_in listen_addr;
listen_addr.sin_family = AF_INET;
listen_addr.sin_port = 0;
listen_addr.sin_addr.s_addr = INADDR_ANY;
// todo: test for error
bind(fd, (struct sockaddr *) &listen_addr, sizeof(listen_addr));

// todo: test for addr
// saddr is the struct sockaddr_in you're connecting to
connect(fd, (struct sockaddr *) &saddr, sizeof(saddr));

Эта опция менее эффективна, потому что вы по-прежнему будете насыщать внутренние структуры данных ядра для TCP-соединений в соответствии с netstat -an | grep -e tcp -e udp | wc -l. Однако вы не начнете повторное использование портов, пока это не произойдет.

Ответ 4

Я получил эту проблему. Я получил это разрешение путем включения tcp timestamp.

Первопричина:

  1. После закрытия соединения, Соединения в течение некоторого времени перейдут в состояние TIME_WAIT.

  2. Во время этого состояния, если какие-либо новые соединения приходят с тем же IP и PORT, если SO_REUSEADDR не предоставлен во время создания сокета, то socket bind() завершится с ошибкой EADDRINUSE.

  3. Но даже если после предоставления SO_REUSEADDR, sockect connect() может завершиться с ошибкой EADDRNOTAVAIL, если временная метка tcp не активирована с обеих сторон.

Решение: Пожалуйста, включите tcp timestamp как на стороне клиента, так и на сервере.

echo 1>/proc/sys/net/ipv4/tcp_timestamps

Причина включения tcp_timestamp:

Когда мы включаем tcp_tw_reuse, сокеты в состоянии TIME_WAIT могут использоваться до истечения срока их действия, и ядро будет пытаться убедиться, что нет никаких конфликтов относительно порядковых номеров TCP. Если мы включим tcp_timestamps, это обеспечит невозможность таких коллизий. Однако нам нужно, чтобы временные метки TCP были активированы на обоих концах. Смотрите определение tcp_twsk_unique для подробностей.

ссылка: https://serverfault.com/questions/342741/what-are-the-ramifications-of-setting-tcp-tw-recycle-reuse-to-1

Ответ 5

Еще одна вещь, которую нужно проверить, - это то, что интерфейс вставлен. Я немного запутался в этом при использовании сетевых пространств имен, поскольку создание нового сетевого пространства имен создает совершенно независимый интерфейс loopback, но не поднимает его (по крайней мере, с версиями версий Debian). Это ускользало от меня какое-то время, поскольку обычно не кажется, что loopback как никогда не работает.