UNIX: разработка сетевых приложений
Параметр сокета SO_LINGER
Этот параметр определяет, как работает функция
для протоколов, ориентированных на установление соединения (например, TCP и SCTP, но не UDP). По умолчанию функцияcloseвозвращает управление немедленно, но если в отправляющем буфере сокета остаются какие-либо данные, система попытается доставить данные собеседнику.closeПараметр сокета
позволяет нам изменять поведение по умолчанию. Для этого необходимо, чтобы между пользовательским процессом и ядром была передана следующая структура, определяемая в заголовочном файлеSO_LINGER:<sys/socket.h>struct linger {int l_onoff; /* 0=off, ненулевое значение=on */ int l_linger;/* время ожидания, в POSIX измеряется в секундах */};Вызов функции
приводит к одному из трех следующих сценариев в зависимости от значений двух элементов структурыsetsockopt.linger1. Если
имеет нулевое значение, параметр выключается. Значениеl_onoffигнорируется и применяется ранее рассмотренный заданный по умолчанию сценарий TCP: функцияl_lingerзавершается немедленно.close2. Если значение
ненулевое, аl_onoffравно нулю, TCP сбрасывает соединение, когда оно закрывается [128, с. 1019–1020], то есть TCP игнорирует все данные, остающиеся в буфере отправки сокета, и отправляет собеседнику сегмент RST, а не обычную последовательность завершения соединения, состоящую из четырех пакетов (см. раздел 2.5). Пример мы покажем в листинге 16.14. Тогда не наступает состояние TCP TIME_WAIT, но из-за этого возникает возможность создания другого воплощения (incarnation) этого соединения в течение 2MSL секунд (удвоенное максимальное время жизни сегмента). Оставшиеся старые дублированные сегменты из только что завершенного соединения могут быть доставлены новому воплощению, что приведет к ошибкам (см. раздел 2.6).l_lingerПри указанных выше значениях
иl_onoffSCTP также выполняет аварийное закрытие сокета, отправляя собеседнику пакет ABORT (см. раздел 9.2 [117]).l_lingerПРИМЕЧАНИЕОтдельные выступления в Usenet звучат в защиту использования этой возможности, поскольку она позволяет избежать состояния TIME_WAIT и снова запустить прослушивающий сервер, даже если соединения все еще используются с известным портом сервера. Так не нужно делать, поскольку это может привести к искажению данных, как показано в RFC 1337 [11]. Вместо этого перед вызовом функции bind на стороне сервера всегда нужно использовать параметр сокета SO_REUSEADDR, как показано далее. Состояние TIME_WAIT — наш друг, так как оно предназначено для того, чтобы помочь нам дождаться, когда истечет время жизни в сети старых дублированных сегментов. Вместо того, чтобы пытаться избежать этого состояния, следует понять его назначение (см. раздел 2.6).
Тем не менее в некоторых обстоятельствах использование аварийного закрытия может быть оправдано. Одним из примеров является сервер терминалов RS-232, который может навечно зависнуть в состоянии CLOSE_WAIT, пытаясь доставить данные на забитый порт. Если же он получит сегмент RST, он сможет сбросить накопившиеся данные и заново инициализировать порт.
3. Если оба значения —
иl_onoff— ненулевые, то при закрытии сокета ядро будет ждать (linger) [128, с. 472]. То есть если в буфере отправки сокета еще имеются какие-либо данные, процесс входит в состояние ожидания до тех пор, пока либо все данные не будут отправлены и подтверждены другим концом TCP, либо не истечет время ожидания. Если сокет был установлен как неблокируемый (см. главу 16), он не будет ждать завершения выполнения функцииl_linger, даже если время задержки ненулевое. При использовании этого свойства параметраcloseприложению важно проверить значение, возвращаемое функциейSO_LINGER. Если время ожидания истечет до того, как оставшиеся данные будут отправлены и подтверждены, функцияcloseвозвратит ошибкуcloseи все данные, оставшиеся в буфере отправки сокета, будут сброшены.EWOULDBLOCKТеперь нам нужно точно определить, когда завершается функция
на сокете в различных сценариях, которые мы рассмотрели. Предполагается, что клиент записывает данные в сокет и вызывает функциюclose. На рис. 7.1 показана ситуация по умолчанию.closeРис. 7.1. Действие функции close, заданное по умолчанию: немедленное завершение
Мы предполагаем, что когда приходят данные клиента, сервер временно занят. Поэтому данные добавляются в приемный буфер сокета его протоколом TCP. Аналогично, следующий сегмент (сегмент FIN клиента) также добавляется к приемному буферу сокета (каким бы образом реализация ни сохраняла сегмент FIN). Но по умолчанию клиентская функция
сразу же завершается. Как мы показываем в этом сценарии, клиентская функцияcloseможет завершиться перед тем, как сервер прочитает оставшиеся данные в приемном буфере его сокета. Если узел сервера выйдет из строя перед тем, как приложение-сервер считает оставшиеся данные, клиентское приложение никогда об этом не узнает.closeКлиент может установить параметр сокета
, задав некоторое положительное время задержки. Когда это происходит, клиентская функцияSO_LINGERне завершается до тех пор, пока все данные клиента и его сегмент FIN не будут подтверждены протоколом TCP сервера. Мы показываем это на рис. 7.2.closeРис. 7.2. Закрытие сокета с параметром SO_LINGER и положительным l_linger
Но у нас остается та же проблема, что и на рис. 7.1: если на узле сервера происходит сбой до того, как приложение-сервер считает оставшиеся данные, клиентское приложение никогда не узнает об этом. Еще худший вариант развития событий показан на рис. 7.3, где значение SO_
было установлено слишком маленьким.LINGERРис. 7.3. Закрытие сокета с параметром SO_LINGER при малом положительном l_linger
Основным принципом взаимодействия является то, что успешное завершение функции
с установленным параметром сокетаcloseговорит нам лишь о том, что данные, которые мы отправили (и наш сегмент FIN) подтверждены протоколом TCP собеседника. Но это не говорит нам, прочитало ли данные приложение собеседника. Если мы не установим параметр сокетаSO_LINGER, мы не будем знать, подтвердил ли другой конец TCP отправленные ему данные.SO_LINGERЧтобы узнать, что сервер прочитал данные клиента, клиент может вызвать функцию
(со вторым аргументомshutdown) вместо функцииSHUT_WRи ждать, когда собеседник закроет с помощью функцииcloseсвой конец соединения. Этот сценарий показан на рис. 7.4.close


