Для моєї пропозиції, будь ласка, прочитайте останній розділ: “Коли використовувати SO_LINGER з таймаутом 0” .
Перш ніж ми підійдемо до цієї невеликої лекції про:
- Звичайне припинення TCP
TIME_WAIT
FIN, ACKіRST
Звичайне припинення TCP
Звичайна послідовність завершення TCP виглядає так (спрощено):
У нас є два однолітки: А і В
- A дзвінки
close()
- A відправляє
FIN B
- А переходить у
FIN_WAIT_1стан
- Б отримує
FIN
- Б відправляє
ACK A
- Б переходить у
CLOSE_WAITстан
- А отримує
ACK
- А переходить у
FIN_WAIT_2стан
- B дзвінки
close()
- Б відправляє
FIN A
- Б переходить у
LAST_ACKстан
- А отримує
FIN
- A відправляє
ACK B
- А переходить у
TIME_WAITстан
- Б отримує
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можливість відкинути очікувані дані. "
Я б порекомендував цю довгу статтю, яка, на мою думку, дає дуже гарну відповідь на ваше запитання.