Коли потрібна опція TCP SO_LINGER (0)?


95

Думаю, я розумію формальне значення варіанту. У деяких застарілих кодах, якими я зараз працюю, використовується опція. Клієнт скаржиться на RST як відповідь на FIN з його боку на з'єднання, розташоване поруч з його боку.

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

Чи можете ви, будь ласка, навести приклад, коли потрібен буде варіант?


1
Ви повинні видалити його. Його не слід використовувати у виробничому коді. Єдиний раз, коли я коли-небудь бачив, що він використовується, це результат недійсного тесту.
Маркіз Лорнський

Відповіді:


82

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

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

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


3
Я цілком погоджуюся. Я бачив програму моніторингу, яка ініціювала багато (кілька тисяч короткочасних з'єднань кожні X секунд), і вона, мабуть, мала масштаб (більше на тисячу з'єднань). Не знаю чому, але аплікатоїн не реагував. Хтось запропонував SO_LINGER = true, TIME_WAIT = 0 швидко звільнити ресурси ОС, і після короткого дослідження ми спробували це рішення з дуже хорошими результатами. TIME_WAIT більше не є проблемою для цього додатка.
bartosz.r

24
Я не погоджуюсь. Протокол рівня додатку, що знаходиться поверх TCP, повинен бути розроблений таким чином, щоб клієнт завжди ініціював підключення закритим. Таким чином, TIME_WAITволя буде сидіти біля клієнта, не завдаючи шкоди. Пам’ятайте, як сказано в третьому виданні «Мережеве програмування UNIX» (Стівенс та ін.), Сторінка 203: «Шлях TIME_WAIT - ваш друг і він нам допомагає. Замість того, щоб намагатися уникати стану, ми повинні це розуміти (Розділ 2.7) . "
mgd

8
Що робити, якщо клієнт хоче відкрити 4000 підключень кожні 30 секунд (ця програма для моніторингу є клієнтом! Оскільки вона ініціює підключення)? Так, ми можемо оновити додаток, додати в інфраструктуру деяких локальних агентів, змінити модель на натискання. Але якщо у нас вже є такий додаток, і він зростає, тоді ми можемо змусити його працювати, налаштувавши тривалість часу. Ви змінюєте один параметр, і у вас раптом з’являється робочий додаток, не вкладаючи коштів на впровадження нової архітектури.
bartosz.r

3
@ bartosz.r: Я лише кажу, що використання SO_LINGER з таймаутом 0 дійсно має бути крайнім засобом. Знову ж таки, у третьому виданні "Мережеве програмування UNIX" (Стівенс та ін.) На сторінці 203 також зазначено, що ви ризикуєте пошкодити дані. Подумайте про читання RFC 1337, де ви зможете зрозуміти, чому TIME_WAIT - ваш друг.
mgd

7
@caf Ні, класичним рішенням буде пул з'єднань, як це видно в кожному важкому API TCP, наприклад HTTP 1.1.
Маркіз Лорнський

188

Для моєї пропозиції, будь ласка, прочитайте останній розділ: “Коли використовувати SO_LINGER з таймаутом 0” .

Перш ніж ми підійдемо до цієї невеликої лекції про:

  • Звичайне припинення TCP
  • TIME_WAIT
  • FIN, ACKіRST

Звичайне припинення TCP

Звичайна послідовність завершення TCP виглядає так (спрощено):

У нас є два однолітки: А і В

  1. A дзвінки close()
    • A відправляє FIN B
    • А переходить у FIN_WAIT_1стан
  2. Б отримує FIN
    • Б відправляє ACK A
    • Б переходить у CLOSE_WAITстан
  3. А отримує ACK
    • А переходить у FIN_WAIT_2стан
  4. B дзвінки close()
    • Б відправляє FIN A
    • Б переходить у LAST_ACKстан
  5. А отримує FIN
    • A відправляє ACK B
    • А переходить у TIME_WAITстан
  6. Б отримує ACK
    • B переходить у CLOSEDстан - тобто видаляється з таблиць сокетів

TIME_WAIT

Отже, рівноправний партнер, який ініціює припинення - тобто дзвонить 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()надішле aRST (скидання з'єднання), яке вказує на стан помилки, і саме так воно сприйматиметься на іншому кінці. Зазвичай ви бачите такі помилки, як "Скидання з’єднання за рівнем".

Отже, в звичайній ситуації дуже погана ідея встановлювати SO_LINGERз тайм-аутом 0 до дзвінка close()- відтепер називається abortive close - у серверній програмі.

Однак певна ситуація вимагає зробити це в будь-якому випадку:

  • Якщо клієнт вашої серверної програми неналежним чином поводиться (час очікування закінчується, повертає недійсні дані тощо), абортивне закриття має сенс уникнути застрягання CLOSE_WAITабо потрапляння в TIME_WAITстан.
  • Якщо вам потрібно перезапустити серверну програму, яка наразі має тисячі клієнтських з'єднань, ви можете розглянути можливість встановлення цієї опції сокета, щоб уникнути тисяч серверних сокетів TIME_WAIT(при виклику close()з кінця сервера), оскільки це може перешкодити серверу отримати доступні порти для нових клієнтських з'єднань після перезапуску.
  • На сторінці 202 у вищезазначеній книзі конкретно сказано: "Існують певні обставини, які вимагають використання цієї функції для переривання закриття. Одним з прикладів є сервер терміналів RS-232, який може назавжди зависнути, CLOSE_WAITнамагаючись доставити дані на застряглий термінал порт, але буде належним чином скинути застряглий порт, якщо він отримає RSTможливість відкинути очікувані дані. "

Я б порекомендував цю довгу статтю, яка, на мою думку, дає дуже гарну відповідь на ваше запитання.


6
TIME_WAITє одним тільки тоді , коли він не починає проблем викликають: stackoverflow.com/questions/1803566 / ...
Pacerier

2
так що, якщо ви пишете веб-сервер? як ви "говорите клієнту ініціювати закриття"?
Шон Ніл

2
@ShaunNeal ти, очевидно, ні. Але добре написаний клієнт / браузер ініціює закриття. Якщо клієнт погано себе поводить, на щастя, у нас є TIME_WAIT вбивство, щоб переконатися, що у нас не закінчуються дескриптори сокетів та ефемерні порти.
mgd

16

Коли затримка ввімкнена, але час очікування дорівнює нулю, стек TCP не чекає відправлення даних, що очікують, перед тим, як закрити з'єднання. Дані можуть бути втрачені через це, але, встановивши затримку таким чином, ви приймаєте це і просите негайно скинути з'єднання, а не витончено закривати. Це спричиняє надсилання RST, а не звичайного FIN.

Дякую EJP за його коментар, див. Тут для деталей.


1
Я це розумію. я прошу "реалістичний" приклад, коли ми хотіли б використовувати жорсткий скидання.
dimba

5
Кожного разу, коли ви хочете перервати зв’язок; отже, якщо ваш протокол не вдається перевірити, і у вас клієнт раптом розмовляє сміття, ви перервете зв’язок з RST тощо
Лен Холгейт,

5
Ви плутаєте нульовий тайм-аут із затримкою. Відставання означає, що close () не блокує. Затримка з позитивним тайм-аутом означає, що блоки () закриваються до часу очікування. Затримка з нульовим тайм-аутом викликає RST, і саме в цьому полягає питання.
Маркіз Лорнський

2
Так, ти маєш рацію. Я скоригую відповідь, щоб виправити свою термінологію.
Лен Холгейт

6

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

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

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

Якщо ваш додаток є «сервером» (закрити другим як реакція на закриття однолітка), при закритті () ваше з’єднання вимикається елегантно, і ресурси звільняються, оскільки ви не переходите в стан TIME_WAIT. Не можна використовувати Linger. Але якщо у вашій програмі sever є наглядовий процес, який виявляє неактивні відкриті з’єднання в режимі очікування протягом тривалого часу (потрібно визначити „довгий“), ви можете вимкнути це неактивне з’єднання зі свого боку - розглядати це як різновид обробки помилок - із негативним вимкненням. Це робиться шляхом встановлення тайм-ауту затримки на 0. close () потім надішле клієнту RST, кажучи йому, що ти злишся :-)


1

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


0

Мені подобається зауваження Максима, що атаки DOS можуть вичерпати ресурси сервера. Це трапляється і без насправді зловмисного суперника.

Деякі сервери мають справу з "ненавмисною атакою DOS", яка виникає, коли у клієнтському додатку виникає помилка з витоком з'єднання, де вони продовжують створювати нове з'єднання для кожної нової команди, яку вони надсилають на ваш сервер. А потім, можливо, врешті-решт закриття своїх з’єднань, якщо вони потраплять під тиск ГХ, або, можливо, з’єднання з часом закінчуються.

Інший сценарій - це коли сценарій "усі клієнти мають однакову адресу TCP". Тоді підключення клієнта можна розрізнити лише за номерами портів (якщо вони підключаються до одного сервера). І якщо клієнти починають швидко циклічно відкривати / закривати з'єднання з будь-якої причини, вони можуть вичерпати (адрес клієнта + порт, сервер IP + порт) простір кортежів.

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


0

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.