Time wait socket windows

Time wait socket windows

В этом разделе рассказывается о том, что такое состояние TIME-WAIT в протоколе TCP, для чего оно служит и почему не следует пытаться обойти его.

Поскольку состояние TIME-WAIT запрятано глубоко в недрах конечного автомата, управляющего работой TCP, многие программисты только подозреваю о его существовании и смутно представляют себе назначение и важность этого с стояния. Писать приложения TCP/IP можно, ничего не зная о состоянии ТIME-WAIT, но необходимо разобраться в странном, на первый взгляд, поведении приложения (указание 23). Это позволит избежать непредвиденных последствий.

Рассмотрим состояние TIME-WAIT и определим, каково его место в работе TCP-соединения. Затем будет рассказано о назначении этого состояния и его важности, а также, почему и каким образом некоторые программисты пытаются обойти это состояние. В конце дано правильное решение этой задачи.

Что это такое

Состояние TIME-WAIT наступает в ходе разрыва соединения. Помните (указание 7), что для разрыва TCP-соединения нужно обычно обменяться четырьмя сегментами, как показано на рис. 3.8.

На рис. 3.8 показано соединение между двумя приложениями, работающими на хостах 1 и 2. Приложение на хосте 1 закрывает свою сторону соединения, при этом TCP посылает сегмент FIN хосту 2. Хост 2 подтверждает FIN сегментом АСК и доставляет FIN приложению в виде признака конца файла EOF (предполагается, что у приложения есть незавершенная операция чтения, — указание 16). Позже приложение на хосте 2 закрывает свою сторону соединения, посылая FIN хосту 1, который отвечает сегментом АСК.

Рис. 3.8. Разрыв соединения

В этот момент хост 2 окончательно закрывает соединение и освобождает ресурсы. С точки зрения хоста 2, соединения больше не существует. Однако хост 1 закрывает соединение, а переходит в состояние TIME-WAIT и остается в нем в течение двух максимальных продолжительностей существования сегмента (2MSL maximum segment lifetime).

Примечание: Максимальное время существования сегмента (MSL) — это максимальное время, в течение которого сегмент может оставаться в сети, прежде чем будет уничтожен. В каждой IР-датаграммеесть поле TTL (time-to-live — время жизни). Это поле уменьшается нa единицу каждым маршрутизатором, через который проходит датаграмма. Когда TTL становится равным нулю, датаграмма уничтожается. Хотя официально TTL измеряется в секундах, в действительности это поле почти всегда интерпретируется маршрутизаторами как счетчик промежуточных узлов. В RFC 1812 [Baker 1995] этот вопрос обсуждается подробнее.

Прождав время 2MSL, хост 1 также закрывает соединение и освобождает ресурсы.

Относительно состояния TIME-WAIT следует помнить следующее:

  • обычно в состояние TIME-WAIT переходит только одна сторона — та, что выполняет активное закрытие;

Примечание: Под активным закрытием понимается отправка первого FIN. Считается, что вторая сторона при этом выполняет пассивное закрытие. Возможно также одновременное закрытие, когда обе стороны закрывают соединение примерно в одно время, поэтому посланные ими FIN одновременно находятся в сети. В этом случае активное закрытие выполняют обе стороны, так что обе переходят в состояние TIME- WAIT.

  • в RFC 793 [Postel 1981b] MSL определено равным 2 мин. При этом соединение должно оставаться в состоянии TIME-WAIT в течение 4 мин. На прак­тике это обычно не так. Например, в системах, производных от BSD, MSL равно 30 с, так что состояние TIME-WAIT длится всего 1 мин. Можно встретить и другие значения в диапазоне от 30 с до 2 мин;
  • если в то время, когда соединение находится в состоянии TIME-WAIT, прибывает новый сегмент, то таймер на 2MSL перезапускается. Это будет рассматриваться ниже.
Читайте также:  Mac os lamp install

Зачем нужно состояние TIME- WAIT

Состояние TIME-WAIT служит двум целям:

  • не дать соединению пропасть при потере последнего АСК, посланного активной стороной, в результате чего другая сторона повторно посылает FIN;
  • дать время исчезнуть «заблудившимся сегментам», принадлежащим этому соединению.

Рассмотрим каждую из этих причин. В момент, когда сторона, выполняющая активное закрытие, готова подтвердить посланный другой стороной FIN, все данные, отправленные другой стороной, уже получены. Однако последний АСК может потеряться. Если это произойдет, то сторона, выполняющая пассивное закрытие, обнаружит тайм-аут и пошлет свой FIN повторно (так как не получила АСК на последний порядковый номер).

А теперь посмотрим, что случится, если активная сторона не перейдет в состояние TIME-WAIT, а просто закроет соединение. Когда прибывает повторно переданный FIN, у TCP уже нет информации о соединении, поэтому он посылает в ответ RST (сброс), что для другой стороны служит признаком ошибки, а не нормального закрытия соединения. Но, так как сторона, пославшая последний АСК, все-таки перешла в состояние TIME-WAIT, информация о соединении еще хранится, так что она может корректно ответить на повторно отправленный FIN.

Этим объясняется и то, почему 2МSL-таймер перезапускается, если в состоянии TIME-WAIT приходит новый сегмент. Если последний АСК потерян, и другая сторона повторно послала FIN, то сторона, находящаяся в состоянии TIME-WAIT, еще раз подтвердит его и перезапустит таймер на случай, если и этот АСК будет потерян.

Второе назначение состояния TIME-WAIT более важно. Поскольку IР-дата-граммы могут теряться или задерживаться в глобальной сети, TCP использует механизм подтверждений для своевременной повторной передачи неподтвержден­ных сегментов (указание 1). Если датаграмма просто задержалась в пути, но не потеряна, или потерян подтверждающий ее сегмент АСК, то после прибытия исходных данных могут поступить также и повторно переданные. TCP в этом случае определяет, что порядковые номера поступивших данных находятся вне текущего окна приема, и отбрасывает их.

А что случится, если задержавшийся или повторно переданный сегмент придет после закрытия соединения? Обычно это не проблема, так как TCP просто отбросит данные и пошлет RST. Когда RST дойдет до хоста, отправившего задержавшийся сегмент, то также будет отброшен, поскольку у этого хоста больше нет информации о соединении. Однако если между этими двумя хостами установлено новое соединение с такими же номерами портов, что и раньше, то заблудившийся сегмент может быть принят как принадлежащий новому соединению. Если порядковые номера данных в заблудившемся сегменте попадают в текущее окно приема нового соединения, то данные будут приняты, следовательно, новое соединение — скомпрометировано.

Состояние TIME-WAIT предотвращает такую ситуацию, гарантируя, что два прежних сокета (два IP-адреса и соответствующие им номера портов) повторно не используются, пока все сегменты, оставшиеся от старого соединения, не будут уничтожены. Таким образом, вы видите, что состояние TIME-WAIT играет важную роль в обеспечении надежности протокола TCP. Без него TCP не мог бы гарантировать доставку данных по порядку и без искажений (указание 9).

Принудительная отмена состояния TIME-WAIT

К сожалению, иногда можно досрочно выйти из состояния TIM Е-WAIT. Это называется принудительной отменой (TIME-WAIT assassination) и бывает случай но или намеренно.

Сначала посмотрим, как это может произойти случайно. По стандарта RFC 793, если соединение находится в состоянии TIME-WAIT и приходит RST то соединение должно быть немедленно закрыто. Предположим, что имеет единение в состоянии TIME-WAIT и приходит старый сегмент-дубликат, который TCP не принимает (например, потому, что порядковый номер оказался вне окна приема). TCP посылает в ответ АСК, в котором указано, какой порядковый номер он ожидает (следующий за номером сегмента FIN, посланного другой стороной). Но у хоста на другой стороне уже нет информации о соединении, поэтому этот АСК он отвечает сегментом RST. Когда этот RST приходит хосту, у которого соединение находится в состоянии TIME-WAIT, тот немедленно закрывает соединение, — состояние TIME-WAIT принудительно отменено.

Читайте также:  Как изменить alias linux

Эта ситуация описана в RFC 1337 [Braden 1992b], где также рассматриваются трудности, сопряженные с принудительной отменой состояния TIME-WAIT. Опасность состоит в возможности «воскрешения» старого соединения (то есть появления соединения с теми же двумя сокетами), что может привести к подтверждению старых данных, десинхронизации соединения с входом в бесконечный цикл и к ошибочному завершению нового соединения.

Это легко предотвратить, изменив протокол TCP так, чтобы в состоянии TIME-WAIT было разрешено игнорировать RST Хотя такое изменение, рекомендованное в RFC 1337, официально не одобрено, тем не менее в некоторых стеках оно реализовано.

Принудительно отменить состояние TIME-WAIT можно и намеренно. С помощью опции сокета SO_LINGER программист требует немедленного закрытия соединения даже в том случае, когда приложение выполняет «активное закрытие. Этот сомнительный прием иногда рекомендуют применять, чтобы вывести «упавший» сервер из состояния TIME-WAIT и запустить его заново. Подробнее об этой проблеме и более правильном способе ее решения будет рассказано в указании 23. Корректно написанное приложение никогда не должно манипулировать состоянием TIME-WAIT, поскольку это неотъемлемая часть механизма обеспечения надежности TCP.

Обычно, когда приложение закрывает соединение, вызов close или closesocket возвращается немедленно, даже если в буфере передачи еще есть данные. Разумеется, TCP будет пытаться доставить эти данные, но приложение не имеет информации, удалось ли это. Чтобы решить эту проблему, можно установить опцию сокета SO_LINGER. Для этого следует заполнить структуру linger и вызывать setsockopt с параметром SO_LINGER.

В большинстве UNIX-систем структура linger определена в заголовочном файле /usr/include/sys/socket.h. В системе Windows она находится в файле winsock2.h. В любом случае она выглядит так:

int l_onoff; /*Включить/выключить опцию.*/

int l_linger; /*Время задержки.*/

Если поле l_onoff равно нулю, то опция задержки отключается, и выбирает поведение по умолчанию — вызов close или closesocket возвращается немедленно, а ядро продолжает попытки доставить еще не переданные данные. Если же l_onoff не равно нулю, то работа зависит от значения поля l_linger. Если l_linger отлично от нуля, то считается, что это время, в течение которого ядро должно подождать отправки и подтверждения оставшихся в буфере передает, данных. При этом close или closesocket не возвращается, пока данные не будут доставлены или не истечет указанное время.

Если к моменту завершения ожидания данные еще не доставлены, то close или closesocket возвращает код EWOULDBLOCK, и недоставленные данные могут быть потеряны. Если все данные уже доставлены, то оба вызова возвращают нуль.

Примечание: К сожалению, семантика поля l_linger зависит от реализации. В Windows и некоторых реализациях UNIX это число секунд, на которое следует задержать закрытие сокета. В системах, производных от BSD, это число тактов таймера (хотя в документации сказано, что это число секунд).

Используя опцию SO_LINGER таким способом, вы гарантируете, что данные будут доставлены уровню TCP на удаленном хосте. Но они не обязательно будут прочитаны приложением. Более правильный способ добиться последнего — использовать процедуру аккуратного размыкания, описанную в указании 16.

Читайте также:  Windows internet explorer данная

Если поле l_linger равно нулю, то соединение разрывается. Это означает, что хосту на другом конце посылается RST, и соединение закрывается немедленно, не переходя в состояние TIME-WAIT. Это преднамеренная принудительная отмена состояния TIME-WAIT, о которой упоминалось выше. Как было сказано, это опасный прием, который в обычном приложении применять не следует.

Резюме

В этом разделе обсуждено состояние TIME-WAIT, которое часто понимают неправильно. Это состояние — важная часть механизма обеспечения надежности протокола TCP, и попытки обойти его неверны. Преждевременный выход из состояния TIME-WAIT может быть обусловлен «естественным» стечением обстоятельств в сети или программой, которая манипулирует опцией SO_LINGER.

Печенюшка

Протокол TCP: состояние TIME-WAIT

При использовании утилиты TCP View от Sysinternals или консольной команды netstat мы часто видим несколько непонятных состояний TCP-соединений. Если слова ESTABLISHED и LISTENING в состоянии соединения не вызывают вопросов, то что такое TIME_WAIT? Ожидание? Ожидание чего?…

Ответ на этот вопрос в полной мере дал мне сегодня Яндекс.

Состояние TIME-WAIT наступает в ходе разрыва соединения. Для разрыва TCP-соединения нужно обычно обменяться четырьмя сегментами, как показано на рисунке.

На рисунке показано соединение между двумя приложениями, работающими на хостах 1 и 2. Приложение на хосте 1 закрывает свою сторону соединения, при этом TCP посылает сегмент FIN хосту 2. Хост 2 подтверждает FIN сегментом АСК и доставляет FIN приложению в виде признака конца файла EOF (предполагается, что у приложения есть незавершенная операция чтения). Позже приложение на хосте 2 закрывает свою сторону соединения, посылая FIN хосту 1, который отвечает сегментом АСК.

В этот момент хост 2 окончательно закрывает соединение и освобождает ресурсы. С точки зрения хоста 2, соединения больше не существует. Однако хост 1 закрывает соединение, а переходит в состояние TIME-WAIT и остается в нем в течение двух максимальных продолжительностей существования сегмента (2MSL maximum segment lifetime).

Состояние TIME-WAIT служит двум целям:

— не дать соединению пропасть при потере последнего АСК, посланного активной стороной, в результате чего другая сторона повторно посылает FIN;
— дать время исчезнуть «заблудившимся сегментам», принадлежащим этому соединению.

How to avoid TIME_WAIT for server sockets? [duplicate]

I know you will mask this as duplicate (question1, question2, question3) but the answers are not what I’m looking for (and I think also other people).
So, I’m refering to socket masters (I love you guys): how can I get bind error(address already in use) if I close the socket?
I will describe my problem.

I’ve got a client that communicate with a server
In the server, I have two sockets: sockS (the main socket, that listens) and sockTX (the client one)
If I call my programs once, the communication is fine, then I close both sockets
If I recall server and client, I get the error and I’ve to wait the TIME_WAIT (

3 minutes seconds on Ubuntu 32bit)

Why, after closing both sockets I still get bind error?
There’s a way to make it works without any magic (SO_REUSEADDR)?
I know something in my code is wrong.

Thank you everyone.

That’s the code:
Client

Thank you everyone

EDIT 1: Possible solution (not very beautiful, but just a bit more than SOCK_REUSEADDR)
Try to add, before shutdown and close of the two server’s socket, a sleep(1).
The client will close before the server and it will work
Not very beautiful, I know.

Or, better, before closing the first socket inside the server, you can check if the client closed the connection (like here)

Оцените статью