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

113

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

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

Рехан Хваджа
источник
1
Если вы находитесь здесь из-за «слишком много TIME_WAITна сервере» , просто пропустите первые три ответа, которые избегают вопроса, а не отвечают на него.
Pacerier

Ответы:

148
/etc/init.d/networking restart

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

То же самое относится и к управляющим сигналам, таким как запрос / ответ на завершение. RFC 793 определяет состояние TIME-WAIT следующим образом:

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 устанавливает время ожидания, равное удвоенному максимальному сроку службы сегмента, или 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), все равно продолжайте его использовать. Если он занят, но с другим состоянием, вы все равно получите адрес, который уже используется. Это полезно, если ваш сервер был выключен, а затем сразу же перезапущен, пока сокеты все еще активны на своем порту. Вы должны знать, что если появятся какие-либо неожиданные данные, это может запутать ваш сервер, но, хотя это возможно, это маловероятно.

Евгений Йокота
источник
8
Отличный ответ, но не правильный ответ на его вопрос. Перезагрузка сети будет работать, но затем перезагрузится, так что это не может быть правильно.
Крис Хуан-Ливер
3
@Chris Huang-Leaver, вопрос в том, «Есть ли программа, которую вы можете запустить, чтобы немедленно вывести этот сокет из состояния TIME_WAIT?» если перезагрузка может рассматриваться как запуск программы, то это тоже будет правильный ответ. Почему вы думаете, что это не может быть правильно?
Юджин Йокота
8
У WMR есть самый полезный ответ (именно это я и делаю, когда сталкиваюсь с подобной проблемой). Перезапуск сети слишком решителен, чтобы его можно было решить, и это может занять больше времени, чем простое ожидание тайм-аута. Правильный ответ на его вопрос - «Нет», но ТАК не позволит вам ввести двухбуквенные ответы :-)
Крис Хуанг: Leaver
6
о, хорошо, когда в следующий раз какой-нибудь процесс зависнет на SIGTERM, я просто сломаю свой компьютер вместо того, чтобы починить его.
Longpoke
Обобщением этого является «перезапустить сетевые службы». Конкретное расположение /etc/init.d/networkingзависит от платформы (Debian?), Поэтому точная командная строка будет отличаться (иногда довольно радикально) для других систем. Я согласен с другими комментаторами, что это кажется серьезным излишним и явно разрушительным для любых несвязанных сетевых сервисов.
tripleee
51

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

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

WMR
источник
но я не получил уже связанную ошибку. когда я снова запускаю программу, она прослушивает сообщение (123456), также я вижу, что система показывает TIME_WAIT для этого порта, но все же я могу подключиться. Почему?
Джаяпал Чандран
2
Даже с SO_REUSEADDR все еще возможно получить ошибку «Адрес уже используется». Для получения дополнительной информации см. Hea-www.harvard.edu/~fine/Tech/addrinuse.html .
Цзинго Яо
@WMR SO_REUSEADDRне «закрывает» сокет. Это просто позволяет вам использовать те, которые уже открыты. Таким образом, вопрос по-прежнему "Как принудительно закрыть сокет TIME_WAIT?"
Pacerier
Это правильный ответ, но вопрос был не совсем правильным. По крайней мере, хорошо решил мою проблему (не как перезапустить всю сеть, также разрывая все другие соединения).
V-Mark
SO_REUSEADDRпозволит bind()продолжить; но если вы затем захотите прослушать этот сокет, все равно listen()вернетесь EADDRINUSE. Другими словами, этот ответ может помочь клиентскому программному обеспечению, использующему эфемерные порты, но не решает проблему для серверного программного обеспечения.
Будет
33

Насколько я знаю, нет способа принудительно закрыть сокет вне записи лучшего обработчика сигнала в вашу программу, но есть файл / 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, если порт уже используется. Поэтому, если у вас есть контроль над исходным кодом, вы можете изменить это поведение, чтобы подождать несколько секунд и повторить попытку на том же порту, вместо увеличения.

Ли Колдуэлл
источник
думаю, что вторые два примера должны быть s / rw / tw /, которые я отредактировал бы, но не хватает реп.
1
Взято из документации ядра: Осторожно. И tcp_tw_recycle, и tcp_tw_reuse могут вызвать проблемы. Вы не должны включать ни того, ни другого, не понимая топологию сети между узлами, которые используют или используют узел, где включен параметр. Соединения, которые проходят через узлы, которые знают о состояниях соединения TCP, такие как брандмауэр, NAT или балансировщик нагрузки, могут начать отбрасывать кадры из-за настройки. Проблема станет видимой, когда будет достаточно большое количество соединений.
Установка его 1работает для будущих соединений, но как насчет тех текущих, которые уже открыты?
Pacerier
18

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

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

akostadinov
источник
3

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


источник
2
Он также теряет все исходящие данные, которые все еще находятся в пути, и может вызвать ошибку на другом конце. Не рекомендуется.
user207421
@EJP Неудача рано - почти всегда правильный вызов. Сеть не надежна, и борьба, которая замедлит ход событий. Аварийное приложение не может предположить, что какие-либо данные были сделаны безопасно.
Тобу
1
На самом деле, я бы рекомендовал это в любой день, когда другой конечной точкой является глючный встроенный промышленный шинный шлюз, который реализует свой собственный надежный транспорт на уровне приложений по TCP, где указанный транспорт предотвращает закрытие соединения, если только он не получает RST и, таким образом, заполняется предел соединения на этом шлюзе. Там. Я привел вам очень конкретный и очень реальный пример, который, к сожалению, требует использования таких хаков.
andyn
@Tobu Networking ненадежен, но TCP старается быть таким, и усугублять это не значит делать что-то лучше, а позволить TCP выполнять свою работу не значит «бороться» с чем-либо.
user207421
2

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

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

Эндрю Пэйт
источник
0

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


источник