2. Получив сегмент FIN, сервер шлет в ответ подтверждение ACK. Любые последующие попытки сервера прочитать данные из сокета будут приводить к получению символа конца файла (то есть значения 0).
3. Позже, когда сервер закроет свой конец соединения, он пошлет клиенту сегмент FIN.
4. В ответ на полученный сегмент FIN клиент отправляет подтверждение ACK.
Флаг FIN (по аналогии с флагом SYN и по той же причине) занимает один байт от места, выделенного для порядкового номера соединения. Именно поэтому на рис. 57.6 подтверждение получения сегмента FIN M показано как ACK M+1.
Рис. 57.6.
57.6.6. Вызов shutdown() для TCP-сокета
В предыдущих разделах подразумевалось, что мы выполняем полное закрытие — то есть закрываем оба канала потокового сокета с помощью вызова close(). Но, как отмечалось в разделе 57.2, можно воспользоваться вызовом shutdown() и закрыть только один канал соединения (выполнив тем самым частичное закрытие). В этом разделе мы рассмотрим некоторые особенности поведения вызова shutdown() в контексте TCP-сокетов.
Передавая аргументу how значение SHUT_WR или SHUT_RDWR, мы инициируем процедуру разрыва соединения (то есть активного закрытия), описанную в подразделе 57.6.5; при этом неважно, ссылается ли на данный сокет какой-нибудь другой файловый дескриптор. Далее локальный сокет переходит сначала в состояние FIN_WAIT1, а затем в FIN_WAIT2, тогда как удаленный сокет переходит в состояние CLOSE_WAIT (см. рис. 57.6). Если аргументу how передать значение SHUT_WR, то удаленный сокет сможет продолжать передавать данные, так как его файловый дескриптор остается актуальным, и считывающий канал соединения по-прежнему открыт.
Операция SHUT_RD не имеет смысла в контексте TCP-сокетов. Дело в том, что большинство реализаций протокола TCP не обеспечивают предсказуемое поведение при использовании этой константы, а итоговый результат может варьироваться. В Linux и нескольких других системах в результате операции SHUT_RD (и после прочтения оставшихся данных) вызов read() возвращает символ конца файла, чего и следует ожидать согласно описанию данной константы в разделе 57.2. Но если после этого удаленное приложение запишет какую-либо информацию в свой сокет, то ее по-прежнему можно будет прочитать на другом конце соединения.
В некоторых других системах (таких как BSD) операция SHUT_RD в самом деле приводит к тому, что последующие вызовы read() всегда возвращают 0. Но если в этом случае удаленное приложение продолжит записывать данные в сокет, то соответствующий канал соединения в какой-то момент заполнится, после чего любые (блокирующие) вызовы write() на удаленной стороне будут блокироваться. (Будь потоковый сокет в домене UNIX, при продолжении записи после выполнения операции SHUT_RD удаленное приложение получило бы сигнал SIGPIPE и ошибку EPIPE.)
В целом в портируемых приложениях, работающих с протоколом TCP, следует избегать использования значения SHUT_RD.
57.6.7. Состояние TIME_WAIT
Состояние TIME_WAIT часто вызывает недопонимание у программистов, пишущих сетевые приложения. Если взглянуть на рис. 57.4, то можно увидеть, что через это состояние проходит сокет, выполняющий активное закрытие. Оно служит двум целям:
• реализация надежного разрыва соединения;
• возможность автоматического удаления старых дублирующих сегментов в сети, так предотвращается их прием новыми экземплярами предыдущих соединений.
Состояние TIME_WAIT является особенным в том смысле, что событие, которое позволяет из него выйти (и перейти в CLOSED), заключается в истечении времени ожидания. Оно равно двум
Восьмиразрядное поле TTL (от англ. time-to-live — «время жизни») в IP-заголовке гарантирует, что все IP-пакеты рано или поздно перестанут быть действительными, если не достигнут адресата за фиксированное количество переходов по маршруту между начальным и конечным узлами. MSL является оценкой максимального времени, которое может уйти на исчерпание ограничения TTL. Так как поле TTL занимает 8 бит, оно делает возможным максимум 255 переходов. Это намного больше, чем обычно требуется для прохождения полного маршрута. Исчерпание данного ограничения может быть связано с определенными аномалиями в работе маршрутизатора (например, если он неправильно сконфигурирован), из-за которых пакет попадает в замкнутый круг и не может оттуда выбраться, пока не превысит значение TTL.
В системах семейства BSD значение MSL равно 30 секундам, и Linux следует этой норме. Таким образом, в Linux состояние TIME_WAIT может длиться на протяжении 60 секунд. Хотя документ RFC 1122 рекомендует для MSL значение в 2 минуты. Многие системы следуют данной рекомендации, в связи с чем продолжительность состояния TIME_WAIT может достигать 4 минут.