Параметр 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 клиенту, сообщив ему, что вы сердитесь :-)