По поводу моего предложения прочтите последний раздел: «Когда использовать SO_LINGER с таймаутом 0» .
Прежде чем мы перейдем к небольшой лекции о:
- Обычное завершение TCP
TIME_WAIT
FIN
, ACK
иRST
Обычное завершение TCP
Обычная последовательность завершения TCP выглядит так (упрощенно):
У нас есть два партнера: A и B
- Звонки
close()
- A отправляет
FIN
B
- А переходит в
FIN_WAIT_1
состояние
- B получает
FIN
- B отправляет
ACK
A
- B переходит в
CLOSE_WAIT
состояние
- Получает
ACK
- А переходит в
FIN_WAIT_2
состояние
- B звонки
close()
- B отправляет
FIN
A
- B переходит в
LAST_ACK
состояние
- Получает
FIN
- A отправляет
ACK
B
- А переходит в
TIME_WAIT
состояние
- B получает
ACK
- B переходит в
CLOSED
состояние - т.е. удаляется из таблиц сокетов
ВРЕМЯ ЖДЕТ
Таким образом, одноранговый узел, который инициирует завершение - т.е. звонит close()
первым, - окажется в TIME_WAIT
состоянии.
Чтобы понять, почему TIME_WAIT
штат - наш друг, прочтите раздел 2.7 в третьем издании «Сетевое программирование UNIX» Стивенса и др. (Стр. 43).
Однако это может быть проблемой при наличии большого количества сокетов TIME_WAIT
на сервере, поскольку в конечном итоге это может помешать принятию новых подключений.
Чтобы обойти эту проблему, я видел, как многие предлагали установить параметр сокета SO_LINGER с таймаутом 0 перед вызовом close()
. Однако это плохое решение, поскольку оно приводит к завершению TCP-соединения с ошибкой.
Вместо этого разработайте протокол своего приложения таким образом, чтобы завершение соединения всегда инициировалось со стороны клиента. Если клиент всегда знает, когда он прочитал все оставшиеся данные, он может инициировать последовательность завершения. Например, браузер знает из Content-Length
HTTP-заголовка, когда он прочитал все данные и может инициировать закрытие. (Я знаю, что в HTTP 1.1 он будет некоторое время держать его открытым для возможного повторного использования, а затем закроет.)
Если серверу необходимо закрыть соединение, разработайте протокол приложения так, чтобы сервер запрашивал у клиента вызов close()
.
Когда использовать SO_LINGER с таймаутом 0
Опять же, согласно третьему изданию "UNIX Network Programming", страница 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
отмену ожидающих данных ".
Я бы порекомендовал эту длинную статью, которая, как мне кажется, дает очень хороший ответ на ваш вопрос.