Когда требуется опция TCP SO_LINGER (0)?

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

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

Не могли бы вы привести пример, когда потребуется такая опция?


person dimba    schedule 21.09.2010    source источник
comment
Вы должны удалить это. Его не следует использовать в производственном коде. Единственный раз, когда я когда-либо видел, что он использовался, был результат неверного теста.   -  person user207421    schedule 24.08.2014


Ответы (8)


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

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

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

person caf    schedule 21.09.2010
comment
Я абсолютно согласен. Я видел приложение для мониторинга, которое инициировало множество (несколько тысяч кратковременных соединений каждые X секунд), и у него была возможность масштабирования (на тысячу соединений больше). Я не знаю почему, но заявка не откликнулась. Кто-то предложил SO_LINGER = true, TIME_WAIT = 0 для быстрого освобождения ресурсов ОС, и после недолгого исследования мы все же попробовали это решение с очень хорошими результатами. TIME_WAIT больше не проблема для этого приложения. - person bartosz.r; 14.03.2012
comment
Я не согласен. Протокол прикладного уровня поверх TCP должен быть спроектирован таким образом, чтобы клиент всегда инициировал закрытие соединения. Таким образом, TIME_WAIT будет сидеть у клиента, не причиняя вреда. Помните, как говорится в третьем издании сетевого программирования UNIX (Стивенс и др.), Стр. 203: Состояние TIME_WAIT - ваш друг, и оно должно нам помочь. Вместо того, чтобы пытаться избежать состояния, мы должны его понять (раздел 2.7). - person mgd; 26.10.2012
comment
Что делать, если клиент хочет открывать 4000 подключений каждые 30 секунд (это приложение мониторинга является клиентом! Потому что оно инициирует подключение)? Да, мы можем переделать приложение, добавить несколько локальных агентов в инфраструктуру, изменить модель на push. Но если у нас уже есть такое приложение и оно растет, то мы можем заставить его работать, настроив твингер. Вы меняете один параметр, и у вас внезапно появляется работающее приложение, не вкладывая средств в реализацию новой архитектуры. - person bartosz.r; 29.10.2012
comment
@ bartosz.r: Классическим решением было бы назначить несколько IP-адресов машине, на которой запущено приложение для мониторинга, давая ей больше локальных адресов для использования. - person caf; 29.10.2012
comment
@caf Я никогда об этом не слышал. Но тогда вам придется повторять это назначение нескольких IP-адресов на каждой машине, которую вы перемещаете, каждый раз, когда вы перемещаетесь (при обновлении оборудования, ОС ...). Это еще одна конфигурация, которой нужно управлять, и если вы установите Linger, это будет то, что принадлежит вам в конфигурации ваших приложений. - person bartosz.r; 29.10.2012
comment
@ bartosz.r: Я только говорю, что использование SO_LINGER с таймаутом 0 действительно должно быть последним средством. Опять же, в третьем издании UNIX Network Programming (Stevens et al) на странице 203 также говорится, что вы рискуете повредить данные. Прочтите RFC 1337, где вы поймете, почему TIME_WAIT - ваш друг. - person mgd; 30.10.2012
comment
@mgd, вы правильно подметили. Все это нужно знать и протестировать, прежде чем использовать в дикой природе. Я могу только сказать, что в моей ситуации есть быстрые и короткие передачи, поэтому случайные числа Seq в TCP / IP (как описано в RFC 1337) должны заботиться о старых пакетах. По крайней мере, я пока не видел никакой коррупции. - person bartosz.r; 30.10.2012
comment
Отключение SO_LINGER не позволяет избежать большого количества подключений, находящихся в состоянии TIME_WAIT. - person Jingguo Yao; 27.06.2013
comment
@JingguoYao Правильно. Это делает установка с нулевым таймаутом. Это отвратительная практика, которую следует избегать любой ценой. - person user207421; 24.08.2014
comment
@caf Нет, классическим решением будет пул соединений, как это видно в каждом мощном TCP API, например HTTP 1.1. - person user207421; 24.08.2014
comment
Я согласен с тем, что SO_LINGER с таймаутом 0 - это последнее средство, но я считаю это необходимым для HTTP-серверов (или других обходных путей вместо SO_LINGER). HTTP - это протокол с недолговечными соединениями, когда сервер инициирует закрытие соединения. Высокопроизводительный HTTP-сервер возможен только в том случае, если сервер сразу же забывает о соединении после вызова close (). Также close () должен быть неблокирующим для однопоточного HTTP-сервера, поэтому установка времени ожидания больше нуля нецелесообразна. - person Mike76; 08.04.2019

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

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

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

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

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

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

  1. A calls close()
    • A sends FIN to B
    • А переходит в состояние FIN_WAIT_1
  2. B receives FIN
    • B sends ACK to A
    • B переходит в CLOSE_WAIT состояние
  3. A receives ACK
    • A goes into FIN_WAIT_2 state
  4. B calls close()
    • B sends FIN to A
    • B переходит в LAST_ACK состояние
  5. A receives FIN
    • A sends ACK to B
    • А переходит в состояние TIME_WAIT
  6. B receives ACK
    • B goes to CLOSED state – i.e. is removed from the socket tables

ВРЕМЯ ЖДЕТ

Таким образом, одноранговый узел, который инициирует завершение, то есть сначала вызывает close(), окажется в состоянии 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 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, чтобы отбросить ожидающие данные ».

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

person mgd    schedule 26.10.2012
comment
TIME_WAIT - друг только тогда, когда он не начинает вызывать проблемы: stackoverflow.com/questions/1803566/ - person Pacerier; 23.01.2016
comment
так что, если вы пишете веб-сервер? как вы скажете клиенту инициировать закрытие? - person Shaun Neal; 10.03.2016
comment
@ShaunNeal, ты явно не знаешь. Но хорошо написанный клиент / браузер инициирует закрытие. Если клиент ведет себя плохо, к счастью, у нас есть TIME_WAIT убийство, чтобы гарантировать, что у нас не закончатся дескрипторы сокетов и эфемерные порты. - person mgd; 10.03.2016

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

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

person Len Holgate    schedule 21.09.2010
comment
Это я поняла. то, что я прошу, - это реалистичный пример, когда мы хотели бы использовать полный сброс. - person dimba; 21.09.2010
comment
Всякий раз, когда вы хотите прервать соединение; поэтому, если ваш протокол не прошел проверку, и у вас есть клиент, который говорит вам чушь, вы внезапно прервете соединение с помощью RST и т. д. - person Len Holgate; 21.09.2010
comment
Вы путаете тайм-аут с нулевой задержкой с отключением задержки. Задержка означает, что close () не блокируется. Задержка с положительным таймаутом означает, что close () блокируется до тайм-аута. Задержка с нулевым таймаутом вызывает RST, и вот в чем вопрос. - person user207421; 24.08.2014
comment
Да, ты прав. Я откорректирую ответ, чтобы поправить свою терминологию. - person Len Holgate; 24.08.2014

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

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

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

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

person Grandswiss    schedule 11.01.2018

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

person Maxim Egorushkin    schedule 19.12.2019

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

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

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

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

person Tim Lovell-Smith    schedule 13.01.2020

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

person Gregg Wonderly    schedule 28.02.2020

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

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

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

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

person stconnell    schedule 14.07.2021