Опция TCP SO_LINGER (ноль) - когда это требуется

Я думаю, что я понимаю формальное значение варианта. В некотором устаревшем коде, который я сейчас обрабатываю, используется опция. Заказчик жалуется на RST как ответ на FIN со своей стороны при близком соединении со своей стороны.

Я не уверен, что смогу удалить его безопасно, так как не понимаю, когда его следует использовать.

Можете ли вы привести пример, когда этот вариант потребуется?

8 ответов

Решение

Типичная причина установить SO_LINGER тайм-аут нуля, чтобы избежать большого количества соединений, сидящих в TIME_WAIT состояние, связывая все доступные ресурсы на сервере.

Когда TCP-соединение закрыто чисто, конец, который инициировал закрытие ("активное закрытие"), заканчивается соединением, находящимся в TIME_WAIT на несколько минут. Таким образом, если ваш протокол является тем, где сервер инициирует закрытие соединения и включает в себя очень большое количество недолговечных соединений, то он может быть подвержен этой проблеме.

Это не очень хорошая идея - TIME_WAIT существует по причине (чтобы гарантировать, что случайные пакеты от старых соединений не мешают новым соединениям). Лучше, если возможно, перепроектировать свой протокол так, чтобы клиент инициировал закрытие соединения.

Для моего предложения, пожалуйста, прочитайте последний раздел: "Когда использовать SO_LINGER с таймаутом 0".

Прежде чем мы перейдем к этой небольшой лекции о:

  • Нормальное завершение TCP
  • TIME_WAIT
  • FIN, ACK а также RST

Нормальное завершение TCP

Обычная последовательность завершения TCP выглядит следующим образом (упрощенно):

У нас есть два сверстника: A и B

  1. Звонки close()
    • Отправляет FIN в Б
    • А входит в FIN_WAIT_1 государство
  2. Б получает FIN
    • Б отправляет ACK к
    • Б переходит в CLOSE_WAIT государство
  3. А получает ACK
    • А входит в FIN_WAIT_2 государство
  4. B звонки close()
    • Б отправляет FIN к
    • Б переходит в LAST_ACK государство
  5. А получает FIN
    • Отправляет ACK в Б
    • А входит в TIME_WAIT государство
  6. Б получает ACK
    • Б идет в 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", третье издание, стр. 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 отменить ожидающие данные."

Я бы порекомендовал эту длинную статью, которая, как мне кажется, дает очень хороший ответ на ваш вопрос.

Когда linger включен, но время ожидания равно нулю, стек TCP не ждет ожидающих данных для отправки, прежде чем закрывать соединение. Данные могут быть потеряны из-за этого, но, установив linger таким образом, вы принимаете это и просите, чтобы соединение было немедленно сброшено, а не закрыто изящно. Это вызывает отправку RST, а не обычного FIN.

Спасибо EJP за его комментарий, подробности смотрите здесь.

Можно ли безопасно удалить задержку в своем коде или нет, зависит от типа вашего приложения: это "клиент" (сначала открывает TCP-соединения и активно его закрывает) или это "сервер" (слушает открытое TCP и закрывать его после того, как другая сторона инициировала закрытие)?

Если ваше приложение имеет вид "клиента" (сначала закрывается) И вы инициируете и закрываете огромное количество подключений к различным серверам (например, когда ваше приложение представляет собой приложение для мониторинга, контролирующее доступность огромного количества различных серверов), ваше приложение проблема в том, что все ваши клиентские соединения застряли в состоянии TIME_WAIT. Затем я бы порекомендовал сократить время ожидания до меньшего значения, чем значение по умолчанию, чтобы по-прежнему корректно завершать работу, но раньше освобождать ресурсы клиентских подключений. Я бы не стал устанавливать тайм-аут на 0, так как 0 не завершается корректно с FIN, но не работает с RST.

Если ваше приложение имеет вид "клиента" и должно извлекать огромное количество небольших файлов с одного и того же сервера, вы не должны инициировать новое TCP-соединение для каждого файла и заканчиваться огромным количеством клиентских соединений в TIME_WAIT, но Держите соединение открытым и извлекайте все данные через одно и то же соединение. Длительный вариант можно и нужно убрать.

Если ваше приложение является "сервером" (секунда закрытия как реакция на закрытие однорангового узла), при закрытии () ваше соединение корректно завершается, а ресурсы освобождаются, поскольку вы не переходите в состояние TIME_WAIT. Задержка не должна использоваться. Но если в вашем северном приложении есть надзорный процесс, который обнаруживает неактивные открытые соединения, которые простаивают в течение длительного времени ("длинное" должно быть определено), вы можете отключить это неактивное соединение со своей стороны - рассматривайте это как вид обработки ошибок - с аварийным завершением работы. Это делается путем установки тайм-аута linger на 0. close() затем отправит RST клиенту, сообщив ему, что вы злитесь:-)

Я только что видел, что в веб- сокетах RFC (RFC 6455) прямо указано, что сервер должен вызывать close()сначала на сокете TCP(!)

Я был в восторге, так как де-факто считаю ответ/сообщения @mgd в этой ветке, а RFC явно идет против этого. Но, возможно, это тот случай, когда установка времени задержки, равного 0, будет приемлемой.

Базовое TCP-соединение в большинстве случаев ДОЛЖНО быть закрыто сначала сервером, чтобы оно удерживало состояние TIME_WAIT, а не клиент.

Мне очень интересно услышать любые мысли/понимание по этому поводу.

Мне нравится замечание Максима о том, что атаки DOS могут истощать ресурсы сервера. Это также происходит без действительно злонамеренного противника.

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

Другой сценарий - сценарий "все клиенты имеют один и тот же TCP-адрес". Тогда клиентские соединения различимы только по номерам портов (если они соединяются с одним сервером). И если клиенты начинают быстро циклически открывать / закрывать соединения по любой причине, они могут исчерпать (адрес клиента + порт, IP-адрес сервера + порт) пространство кортежа.

Поэтому я думаю, что серверам лучше всего посоветовать переключиться на стратегию Linger-Zero, когда они видят большое количество сокетов в состоянии TIME_WAIT - хотя это не исправляет поведение клиента, это может уменьшить влияние.

На серверах вы можете отправить RST вместо того FINпри отключении некорректных клиентов. Это пропускаетFIN-WAIT с последующим TIME-WAIT состояния сокета на сервере, что предотвращает истощение ресурсов сервера и, следовательно, защищает от такого типа атак типа "отказ в обслуживании".

Слушающий сокет на сервере может использовать задержку со временем 0, чтобы иметь доступ к немедленной привязке к сокету и сбросить всех клиентов, чьи соединения еще не завершены. TIME_WAIT - это то, что интересно только тогда, когда у вас есть сеть с несколькими путями и может закончиться неправильно упорядоченными пакетами или иным образом иметь дело с нечетным порядком / временем прибытия сетевых пакетов.

Другие вопросы по тегам