Компьютерные сети. 6-е изд.
Шрифт:
Итак, сегменты, используемые транспортным уровнем, помещаются в пакеты, которыми обменивается сетевой уровень. Эти пакеты, в свою очередь, содержатся во фреймах, которые передает канальный уровень. Получив фрейм, процесс канального уровня обрабатывает его заголовок, и если адрес назначения совпадает с местом доставки, передает содержимое поля пользовательских данных наверх сетевой подсистеме. Cетевая подсистема похожим образом обрабатывает заголовок пакета и передает содержимое поля пользовательских данных пакета наверх транспортной подсистеме. Эта вложенность проиллюстрирована на илл. 6.3.
Илл. 6.3. Вложенность сегментов, пакетов и фреймов
Итак, вернемся к нашему примеру общения клиента и сервера. В результате запроса клиента CONNECT серверу отравляется сегмент, содержащий CONNECTION REQUEST (запрос соединения). Когда он прибывает, транспортная подсистема проверяет, заблокирован ли сервер примитивом LISTEN (то есть готов ли он к обработке запросов). Затем она снимает блокировку сервера и отсылает обратно клиенту сегмент CONNECTION ACCEPTED (соединение принято). Получив этот сегмент, клиент разблокируется, после чего соединение считается установленным.
Теперь клиент и сервер могут обмениваться данными с помощью примитивов SEND и RECEIVE. В простейшем случае каждая из сторон использует блокирующий примитив RECEIVE для перехода в режим ожидания сегмента, который передается другой стороной с помощью SEND. Когда сегмент прибывает, получатель разблокируется. Затем он может обработать полученный сегмент и отправить ответ. Такая схема прекрасно работает, пока обе стороны помнят, чья очередь передавать, а чья — принимать данные.
Обратите внимание, что на транспортном уровне даже простая однонаправленная пересылка данных сложнее, чем на сетевом. Каждый отправленный пакет будет в конце концов подтвержден. Пакеты с управляющими сегментами также подтверждаются, явно или неявно. Эти подтверждения управляются транспортными подсистемами при помощи протокола сетевого уровня и не видны пользователям транспортного уровня. Аналогично транспортные объекты занимаются проблемами таймеров и повторных передач. Все эти механизмы не видны пользователям транспортного уровня, для которых соединение представляется надежным битовым каналом. Биты поступают с одного конца канала и волшебным образом появляются на другом его конце в том же порядке. Эта способность скрывать сложность от пользователей свидетельствует о том, что многоуровневые протоколы являются довольно мощным инструментом.
Когда соединение больше не требуется, оно должно быть разорвано, чтобы освободить место в таблицах двух транспортных подсистем. Разъединение бывает симметричным и асимметричным. В асимметричном варианте одна из сторон может вызвать примитив DISCONNECT, в результате чего другая сторона получает управляющий сегмент DISCONNECTION REQUEST (запрос разъединения) и соединение разрывается.
В симметричном варианте каждое направление закрывается отдельно, независимо от другого. Когда одна сторона выполняет операцию DISCONNECT, это означает, что у нее больше нет данных для передачи, но при этом она все еще готова принимать данные от своего партнера. В этой схеме соединение разрывается, когда обе стороны выполняют операцию DISCONNECT.
Диаграмма состояний для установления и разрыва соединения показана на илл. 6.4. Каждый переход вызывается каким-то событием — операцией, выполненной локальным пользователем транспортной службы, или входящим пакетом. Для простоты мы будем считать, что каждый сегмент подтверждается отдельно. Мы также предполагаем, что используется модель симметричного разъединения, в которой клиент делает первый ход. Обратите внимание на простоту этого примера. Позднее, когда мы будем говорить о TCP, мы рассмотрим более реалистичные модели.
6.1.3. Сокеты Беркли
Теперь рассмотрим другой набор примитивов транспортного уровня — примитивы сокетов, используемые для протокола TCP. Впервые сокеты стали применяться в 1983 году в операционной системе Berkeley UNIX 4.2BSD. Очень скоро они приобрели популярность и сейчас широко используются для интернет-программирования в большинстве операционных систем, особенно UNIX; кроме того, существует специальный API, предназначенный для программирования сокетов в системе Windows — «winsock».
Илл. 6.4. Диаграмма состояний для простой схемы управления соединениями. Переходы, обозначенные курсивом, вызываются поступлением пакетов. Сплошными линиями показана последовательность состояний клиента. Пунктирными линиями показана последовательность состояний сервера
Примитивы сокетов перечислены на илл. 6.5. Модель сокетов во многом похожа на представленную выше, но обладает большей гибкостью и предоставляет больше возможностей. Сегменты, соответствующие этой модели, будут рассматриваться далее в этой главе.
Первые четыре примитива из списка выполняются серверами в указанной последовательности. Примитив SOCKET создает новый сокет и выделяет для него место в таблице транспортной подсистемы. Параметры вызова указывают используемый формат адресов, тип требуемой службы (например, надежный поток байтов) и протокол. В случае успеха SOCKET возвращает обычный файловый дескриптор, используемый при вызове следующих операций, подобно тому, как процедура OPEN работает для файла.
Примитив
Значение
SOCKET (СОКЕТ)
Создать новый сокет (гнездо связи)
BIND (СВЯЗАТЬ)
Связать локальный адрес с сокетом
LISTEN (ОЖИДАТЬ)
Объявить о желании принять соединение; указать размер очереди
ACCEPT (ПРИНЯТЬ)
Пассивно установить входящее соединение
CONNECT (СОЕДИНИТЬ)
Активно пытаться установить соединение
SEND (ОТПРАВИТЬ)
Отправить данные по соединению
RECEIVE (ПОЛУЧИТЬ)
Получить данные у соединения
CLOSE (ЗАКРЫТЬ)
Разорвать соединение
Илл. 6.5. Примитивы сокетов для TCP
У только что созданного сокета нет сетевых адресов. Они назначаются с помощью примитива BIND. После того как сервер привязывает адрес к сокету, с ним могут связаться удаленные клиенты. Вызов SOCKET не создает адрес напрямую, так как некоторые процессы придают своим адресам большое значение (например, они использовали один и тот же адрес годами, и он известен всем).
Далее идет вызов примитива LISTEN, который выделяет место для очереди входящих соединений на случай, если несколько клиентов попытаются соединиться одновременно. В отличие от аналогичного примитива в нашем первом примере, в модели сокетов LISTEN не является блокирующим вызовом.
Чтобы заблокировать ожидание входящих соединений, сервер выполняет примитив ACCEPT. Получив сегмент с запросом соединения, транспортная подсистема создает новый сокет с теми же свойствами, что и у исходного сокета, и возвращает для него файловый дескриптор. При этом сервер может разветвить процесс или поток, чтобы обработать соединение для нового сокета и вернуться к ожиданию следующего соединения для первоначального сокета. ACCEPT возвращает файловый дескриптор, который можно использовать для чтения и записи стандартным способом, как это делается в случае файлов.