Для моєї пропозиції, будь ласка, прочитайте останній розділ: “Коли використовувати 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
можливість відкинути очікувані дані. "
Я б порекомендував цю довгу статтю, яка, на мою думку, дає дуже гарну відповідь на ваше запитання.