Назад | Перейти на главную страницу

Как принудительно закрыть сокет в TIME_WAIT?

Я запускаю определенную программу на Linux, которая иногда дает сбой. Если после этого вы откроете его быстро, он будет прослушивать сокет 49201 вместо 49200, как в первый раз. netstat показывает, что 49200 находится в состоянии TIME_WAIT.

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

/etc/init.d/networking restart

Позвольте мне уточнить. Протокол управления передачей (TCP) разработан как двунаправленный, упорядоченный и надежный протокол передачи данных между двумя конечными точками (программами). В этом контексте термин надежный означает, что он будет повторно передавать пакеты, если они будут потеряны в середине. TCP гарантирует надежность, отправляя обратно пакеты подтверждения (ACK) для одного или нескольких пакетов, полученных от однорангового узла.

То же самое касается сигналов управления, таких как запрос / ответ на завершение. RFC 793 определяет состояние ВРЕМЯ-ОЖИДАНИЕ следующим образом:

TIME-WAIT - означает ожидание, пока пройдет достаточно времени, чтобы убедиться, что удаленный TCP получил подтверждение своего запроса на завершение соединения.

См. Следующую диаграмму состояний TCP:

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

Давайте вызовем первого, который будет называть выходов активным ближе, а второй - пассивным ближе. Когда активный доводчик отправляет FIN, состояние переходит в FIN-WAIT-1. Затем он получает ACK для отправленного FIN, и состояние переходит в FIN-WAIT-2. Как только он получает FIN также от пассивного доводчика, активный доводчик отправляет ACK на FIN, и состояние переходит в TIME-WAIT. Если пассивный доводчик не получил ACK на второй FIN, он повторно передаст пакет FIN.

RFC 793 устанавливает TIME-OUT в два раза больше максимального времени жизни сегмента или 2MSL. Поскольку MSL, максимальное время, в течение которого пакет может перемещаться по Интернету, установлено равным 2 минутам, 2MSL - 4 минутам. Поскольку нет ACK для ACK, активный доводчик не может ничего делать, кроме как ждать 4 минуты, если он правильно придерживается протокола TCP / IP, на всякий случай, если пассивный отправитель не получил ACK для своего FIN (теоретически) .

На самом деле пропущенные пакеты, вероятно, редки, и очень редко, если все это происходит в локальной сети или на одной машине.

Чтобы дословно ответить на вопрос, как насильственно закрыть сокет в TIME_WAIT ?, я все равно буду придерживаться своего исходного ответа:

/etc/init.d/networking restart

Фактически, я бы запрограммировал его так, чтобы он игнорировал состояние TIME-WAIT, используя параметр SO_REUSEADDR, как упоминалось в WMR. Что именно делает SO_REUSEADDR?

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

Я не знаю, есть ли у вас исходный код этой конкретной программы, но если да, вы можете просто установить SO_REUSEADDR через setsockopt(2) который позволяет вам выполнять привязку к одному и тому же локальному адресу, даже если сокет находится в состоянии TIME_WAIT (если этот сокет активно не прослушивает, см. socket(7)).

Для получения дополнительной информации о состоянии TIME_WAIT см. Unix socket FAQ.

Насколько мне известно, невозможно принудительно закрыть сокет, кроме написания лучшего обработчика сигналов в вашей программе, но есть файл / proc, который контролирует, сколько времени занимает тайм-аут. Файл

/proc/sys/net/ipv4/tcp_tw_recycle

и вы можете установить тайм-аут на 1 секунду, выполнив следующие действия:

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

Тем не мение, эта страница содержит предупреждение о возможных проблемах надежности при установке этой переменной.

Также есть связанный файл

/proc/sys/net/ipv4/tcp_tw_reuse

который контролирует, можно ли повторно использовать сокеты TIME_WAIT (предположительно без тайм-аута).

Между прочим, документация ядра предупреждает вас не изменять ни одно из этих значений без «совета / запроса технических экспертов». А я нет.

Программа должна была быть написана для попытки привязки к порту 49200 и последующего увеличения на 1, если порт уже используется. Поэтому, если у вас есть контроль над исходным кодом, вы можете изменить это поведение, чтобы подождать несколько секунд и повторить попытку на том же порте вместо увеличения.

На самом деле есть способ убить соединение - killcx. Они утверждают, что он работает в любом состоянии соединения (что я не проверял). Вам нужно знать интерфейс, через который происходит коммуникация, по-видимому, по умолчанию используется eth0.

ОБНОВЛЕНИЕ: другое решение резак который входит в репозитории некоторых дистрибутивов Linux.

Другой вариант - использовать параметр SO_LINGER с таймаутом 0. Таким образом, при закрытии сокет принудительно закрывается, отправляя RST вместо перехода в режим закрытия FIN / ACK. Это позволит избежать состояния TIME_WAIT и может быть более подходящим для некоторых целей.

Альтернативным решением может быть надежный прокси-сервер или программное обеспечение для переадресации портов, которое прослушивает порт 49200, а затем перенаправляет соединение на один из нескольких экземпляров вашей менее надежной программы, используя разные порты ... На ум приходит HAPROXY.

Между прочим, порт, к которому вы подключаетесь, довольно высок. Вы можете попробовать использовать неиспользованный чуть выше диапазона 0-1024. Ваша система с меньшей вероятностью будет использовать меньший номер порта в качестве временного порта.

TIME_WAIT - наиболее распространенная проблема в архитектуре клиент-сервера программирования сокетов. Подождите несколько секунд, периодически пытаясь решить эту проблему. Для приложений реального времени им нужен сервер должен немедленно встать. Для них есть опция SO_REUSEADDR.