Первый пункт, касающийся назначения состояния TIME_WAIT (обеспечение надежного разрыва соединения), проиллюстрирован на рис. 57.6. На этой диаграмме видно: во время завершения TCP-соединения происходит обмен четырьмя сегментами. Последним из них является подтверждение ACK, которое отправляется стороной, выполняющей активное закрытие, приложению, закрывающему свой сокет в пассивном режиме. Представьте, что данный сегмент затерялся в сети. В таком случае сторона, выполняющая пассивное закрытие, рано или поздно повторит передачу сегмента FIN. Поскольку противоположная сторона остается в состоянии TIME_WAIT на протяжении фиксированного отрезка времени, она все еще может повторно отправить подтверждение ACK. Но если сторона, выполнившая активное соединение, больше недоступна, то не будет иметь никакой информации о состоянии соединения. В данном случае в ответ на повторно отправленный сегмент FIN протокол TCP ответит сегментом RST (сброс), который будет интерпретирован как ошибка. (Это объясняет, почему продолжительность состояния TIME_WAIT вдвое больше MSL: один MSL уходит на отправку завершающего подтверждения ACK удаленному концу и еще один нужен на случай, если сегмент FIN нужно будет отправлять повторно.)
Стороне, выполняющей пассивное закрытие, не нужно переходить в состояние TIME_WAIT, поскольку именно она инициировала завершающий обмен сегментами при разрыве соединения. После отправки сегмента FIN она будет ждать подтверждения ACK от удаленного сокета, и если срок годности этого сегмента завершится до получения ACK, то она отправит его снова.
Чтобы понять второе назначение состояния TIME_WAIT (удаление просроченных дублирующих сегментов), нужно вспомнить такой факт: алгоритм повторной передачи, который применяется в протоколе TCP, может привести к дублированию сегментов, и в зависимости от выбранных маршрутов эти дубликаты могут дойти до адресата уже после разрыва соединения. Допустим, у нас имеется соединение между двумя сокетами: 204.152.189.116 порт 21 (служба FTP) и 200.0.0.1 порт 50000. Теперь представим, что мы его разорвали и позже установили аналогичное соединение с теми же IP-адресами и портами. Таким образом получен новый экземпляр старого соединения. В данном случае протокол TCP должен убедиться: никакие старые дублирующие сегменты из предыдущего экземпляра не будут приняты в качестве корректных данных. Чтобы этого добиться, новый экземпляр нельзя установить, пока хотя бы одна из сторон существующего TCP-соединения находится в состоянии TIME_WAIT.
На интернет-форумах часто спрашивают, как отключить состояние TIME_WAIT, ведь оно может привести к ошибке EADDRINUSE («этот адрес уже занят»), если перезапущенный сервер попытается привязать сокет к адресу, используемому на одном из концов соединения, находящихся в данном состоянии. Теоретически это возможно (см. [Stevens et al., 2004]), как и полностью удалять любые сокеты с состоянием TIME_WAIT (см. [Snader, 2000]). Но такие решения являются нежелательными, поскольку их результат отменяет все гарантии надежности, обеспечиваемые этим состоянием. В разделе 57.10 мы рассмотрим параметр SO_REUSEADDR, с помощью которого можно устранить причины, обычно вызывающие ошибку EADDRINUSE, и притом иметь возможность надежно разрывать соединение, задействуя TIME_WAIT.
Программа netstat отображает состояние сокетов в UNIX- и интернет-доменах. Этот инструмент может пригодиться при отладке приложений, основанных на сокетах. Он доступен в большинстве UNIX-систем, хотя синтаксис его аргументов командной строки может варьироваться в зависимости от реализации.
Если запустить утилиту netstat без параметров, то она выведет информацию о подключенных сокетах в обоих доменах. Чтобы изменить данный вывод, можно использовать целый ряд аргументов командной строки. Некоторые из них перечислены в табл. 57.1.
Таблица 57.1. Параметры команды netstat
Параметр — Описание
— a Выводит информацию о всех сокетах, включая слушающие
— e Выводит дополнительную информацию (включая идентификатор владельца сокета)
— c Постоянно обновляет информацию о сокетах (ежесекундно)
— l Выводит информацию только о слушающих сокетах
— n Выводит IP-адреса, номера портов и имена пользователей в числовом формате
— p Выводит идентификатор процесса и название программы, которой принадлежит сокет
— inet Выводит информацию о сокетах в интернет-домене
— tcp Выводит информацию о TCP-сокетах (потоковых) в интернет-домене
— udp Выводит информацию о UDP-сокетах (датаграммных) в интернет-домене
— unix Выводит информацию о сокетах в домене UNIX
Ниже показан урезанный вывод, который можно получить, если запросить с помощью netstat сведения обо всех сокетах интернет-домена в системе:
$ netstat — a — inet
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 *:50000 *:* LISTEN
tcp 0 0 *:55000 *:* LISTEN
tcp 0 0 localhost: smtp *:* LISTEN