Для моего предложения, пожалуйста, прочитайте последний раздел: «Когда использовать SO_LINGER с таймаутом 0» .
Прежде чем мы перейдем к небольшой лекции о:
- Обычное TCP-завершение
TIME_WAIT
FIN
, ACK
и RST
Обычное TCP-завершение
Обычная последовательность завершения TCP выглядит следующим образом (упрощенно):
У нас есть два партнера: A и B
- A вызывает
close()
- A отправляет
FIN
до B - A переходит в
FIN_WAIT_1
состояние
- B получает
FIN
- B отправляет
ACK
в A - B переходит в
CLOSE_WAIT
состояние
- A получает
ACK
- A переходит в
FIN_WAIT_2
состояние
- B звонит
close()
- B отправляет
FIN
в A - B переходит в
LAST_ACK
состояние
- A получает
FIN
- A отправляет
ACK
в B - A переходит в
TIME_WAIT
состояние
- B получает
ACK
- B переходит в состояние
CLOSED
- т.е. удаляется из таблиц сокетов
TIME_WAIT
Таким образом, узел, который инициирует завершение - т.е. вызывает close()
first - перейдет в состояние TIME_WAIT
.
Чтобы понять, почему состояние TIME_WAIT
является нашим другом, прочитайте раздел 2.7 в третьем издании «Сетевое программирование UNIX» Стивенса и др. (Стр.43).
Однако может возникнуть проблема с большим количеством сокетов в состоянии TIME_WAIT
на сервере, так как это может в конечном итоге предотвратить прием новых соединений.
Чтобы обойти эту проблемуЯ видел, как многие предлагали установить опцию сокета SO_LINGER с таймаутом 0 перед вызовом close()
.Однако это плохое решение, поскольку оно приводит к разрыву TCP-соединения с ошибкой.
Вместо этого разработайте протокол приложения, чтобы прекращение подключения всегда инициировалось со стороны клиента.Если клиент всегда знает, когда он прочитал все оставшиеся данные, он может инициировать последовательность завершения.Например, браузер знает из заголовка HTTP Content-Length
, когда он прочитал все данные и может инициировать закрытие.(Я знаю, что в HTTP 1.1 он некоторое время будет держать его открытым для возможного повторного использования, а затем закроет его.)
Если серверу необходимо закрыть соединение, разработайте протокол приложения, чтобы сервер запрашивалклиент должен позвонить close()
.
Когда использовать SO_LINGER с таймаутом 0
Опять же, в соответствии с «Сетевым программированием UNIX», третья страница издания 202-203, установка SO_LINGER
с таймаутом 0 довызов close()
вызовет нормальную последовательность завершения , а не .
Вместо этого узел, устанавливающий эту опцию и вызывающий close()
, отправит RST
(сброс соединения)что указывает на состояние ошибки, и именно так оно будет восприниматься на другом конце.Обычно вы будете видеть ошибки типа «Сброс соединения по пиру».
Поэтому в обычной ситуации очень плохая идея установить SO_LINGER
с таймаутом 0 до вызова close()
- с этого момента вызывается аварийное закрытие - в серверном приложении.
Тем не менее, определенная ситуация оправдывает это в любом случае:
- Если клиент вашего серверного приложения работает неправильно (время ожидания истекло), возвращает неверные данные и т. д.) при сбое закрытия имеет смысл избегать застревания в
CLOSE_WAIT
или попадания в состояние TIME_WAIT
. - Если вам необходимо перезапустить серверное приложение, в котором на данный момент находятся тысячи клиентовСоединения, которые вы могли бы рассмотреть, установив этот параметр сокета, чтобы избежать тысяч серверных сокетов в
TIME_WAIT
(при вызове close()
со стороны сервера), так как это может помешать серверу получать доступные порты для новых клиентских соединений после перезапуска. - На странице202 в вышеупомянутой книге, в частности, говорится: «Существуют определенные обстоятельства, которые требуют использования этой функции для отправки аварийного закрытия. Одним из примеров является терминальный сервер RS-232, который может зависнуть навсегда в
CLOSE_WAIT
, пытаясь доставить данные застрявшемупорт терминала, но он будет правильно сбрасывать застрявший порт, если он получит RST
, чтобы отбросить ожидающие данные. "очень хороший ответ на ваш вопрос.