Параметр TCP SO_LINGER (ноль) - когда требуется
Думаю, я понимаю формальный смысл этого варианта. В некотором унаследованном коде, который я обрабатываю сейчас, используется опция. Клиент жалуется на RST как ответ FIN со своей стороны на соединение рядом со своей стороной.
Я не уверен, что смогу удалить его безопасно, так как я не понимаю, когда он должен использоваться.
Можете ли вы привести пример того, когда потребуется опция?
Ответы
Ответ 1
Типичная причина установки тайм-аута SO_LINGER
равна нулю, чтобы избежать большого количества подключений, находящихся в состоянии TIME_WAIT
, связывая все доступные ресурсы на сервере.
Когда TCP-соединение закрыто чисто, конец, который инициировал закрытие ( "активное закрытие" ), заканчивается соединением, сидящим в TIME_WAIT
в течение нескольких минут. Поэтому, если ваш протокол является тем, где сервер инициирует соединение, и он включает очень большое количество короткоживущих соединений, тогда он может быть восприимчив к этой проблеме.
Это не очень хорошая идея, однако - TIME_WAIT
существует по какой-либо причине (чтобы гарантировать, что бродячие пакеты из старых подключений не мешают новым соединениям). Лучше всего перепроектировать ваш протокол на тот, где клиент инициирует соединение, если это возможно.
Ответ 2
По моему предложению, пожалуйста, прочитайте последний раздел: "Когда использовать 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()
, сначала - закончится в состоянии TIME_WAIT
.
Чтобы понять, почему состояние TIME_WAIT
является нашим другом, прочитайте раздел 2.7 в третьем издании "Сетевое программирование UNIX" от Stevens и др. (стр. 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()
приведет к тому, что нормальная последовательность завершения не будет инициирована.
Вместо этого параметр peer, устанавливающий этот параметр и вызывающий close()
, отправит RST
(connection reset), который указывает на состояние ошибки, и это будет восприниматься с другой стороны. Обычно вы увидите ошибки, такие как "Соединение reset одноранговым узлом".
Поэтому в нормальной ситуации очень сложно установить SO_LINGER
с таймаутом 0 до вызова close()
- отныне вызываемого abortive close - в серверном приложении.
Однако определенная ситуация оправдывает это так:
- Если клиент вашего серверного приложения ошибочно работает (время ожидания, возвращает недопустимые данные и т.д.), прерывистый закрытие имеет смысл избежать застревания в
CLOSE_WAIT
или в результате в состоянии TIME_WAIT
.
- Если вы должны перезапустить серверное приложение, которое в настоящее время имеет тысячи клиентских подключений, вы можете подумать о настройке этой опции сокета, чтобы избежать тысяч сокетов сервера в
TIME_WAIT
(при вызове close()
с конца сервера), поскольку это может помешать сервер от получения доступных портов для новых клиентских подключений после перезапуска.
- На странице 202 в вышеупомянутой книге в нем конкретно говорится: "Существуют определенные обстоятельства, которые гарантируют использование этой функции для отправки прерывистого закрытия. Одним из примеров является сервер терминалов RS-232, который может вешать навсегда в
CLOSE_WAIT
, пытаясь доставлять данные в застрявший порт терминала, но будет правильно reset застрявшим портом, если он получил RST
, чтобы отбросить ожидающие данные."
Я бы рекомендовал эту длинную статью, которая, как я считаю, дает очень хороший ответ на ваш вопрос.
Ответ 3
Когда задержка включена, но тайм-аут равен нулю, стек TCP не ожидает отправки ожидающих данных перед закрытием соединения. Из-за этого данные могут быть потеряны, но, задерживаясь таким образом, вы принимаете это и просите, чтобы соединение было reset прямо, а не закрыто изящно. Это приводит к отправке RST, а не к обычным FIN.
Благодаря EJP для его комментария, см. здесь.
Ответ 4
Независимо от того, удастся ли вы задерживать код в безопасном режиме или нет, зависит от типа вашего приложения: является ли он "клиентом" (открытие TCP-соединений и его активное закрытие вначале), или это "сервер" (прослушивание открытого и открытого TCP закрыв его после того, как другая сторона начала закрытие)?
Если ваше приложение имеет вкус "клиента" (сначала закрытие) И вы инициируете и закрываете огромное количество подключений к различным серверам (например, когда ваше приложение является приложением мониторинга, контролирующим доступность огромного количества разных серверов) ваше приложение проблема в том, что все ваши клиентские соединения застряли в состоянии TIME_WAIT.Тогда я бы рекомендовал сократить таймаут до меньшего значения, чем по умолчанию, чтобы все еще выключить изящно, но освободить ресурсы клиентских подключений раньше. Я бы не устанавливал таймаут для 0, так как 0 не заканчивается изящно с FIN, но прерывается с RST.
Если ваше приложение обладает вкусом "клиента" и должно получать огромное количество небольших файлов с одного и того же сервера, вы не должны инициировать новое TCP-соединение для каждого файла и в конечном итоге заходить в огромное количество клиентских подключений в TIME_WAIT, но держите соединение открытым и извлекайте все данные по одному и тому же соединению. Опция Linger может и должна быть удалена.
Если ваше приложение является "сервером" (закрытие второй как реакция на закрытие сверстника), при закрытии() ваше соединение отключается изящно, а ресурсы освобождаются, так как вы не входите в состояние TIME_WAIT. Linger не следует использовать. sever приложение имеет надзорный процесс, обнаруживающий неактивные открытые соединения в режиме ожидания в течение длительного времени ("длинный" должен быть определен), вы можете отключить это неактивное соединение с вашей стороны - см. его как обработку ошибок - с прерывистым отключением. Это делается путем установки тайм-аута задержки до 0. close() отправит RST клиенту, сообщив ему, что вы сердитесь :-)