Теперь клиент и сервер могут обмениваться данными с помощью базовых операций SEND и RECEIVE. В простейшем случае каждая из сторон может использовать блокирующую операцию RECEIVE для перехода в режим ожидания сегмента, посылаемого противоположной стороной при помощи операции SEND. Когда сегмент прибывает, получатель разблокируется. Затем он может обработать полученный сегмент и послать ответ. Такая схема прекрасно работает, пока обе стороны помнят, чей черед посылать, а чей — принимать.
Обратите внимание на то, что на транспортном уровне даже простая однонаправленная пересылка данных оказывается сложнее, чем на сетевом уровне. Каждый посланный пакет данных будет, в конце концов, подтвержден. Пакеты, содержащие управляющие сегменты, также подтверждаются, явно или неявно. Эти подтверждения управляются транспортными подсистемами при помощи протокола сетевого уровня и не видны пользователям транспортного уровня. Аналогично транспортные подсистемы должны беспокоиться о таймерах и повторных передачах. Все эти механизмы не видны пользователям транспортного уровня, для которых соединение представляется надежным битовым каналом. Один пользователь помещает в канал биты, которые волшебным образом появляются на другом конце канала в том же порядке. Эта способность скрывать сложность от пользователей свидетельствует о том, что многоуровневые протоколы являются довольно мощным инструментом.
Когда соединение больше не требуется, оно должно быть разорвано, чтобы можно было освободить место в таблицах двух транспортных подсистем. Разъединение существует в двух вариантах: симметричном и асимметричном. В асимметричном варианте любой потребитель транспортных услуг может вызвать примитив DISCONNECT, в результате чего удаленной транспортной сущности будет послан управляющий сегмент DISCONNECTION REQUEST (запрос разъединения). После получения сегмента удаленной транспортной подсистемой соединение разрывается.
В симметричном варианте каждое направление закрывается отдельно, независимо от другого. Когда одна сторона выполняет операцию DISCONNECT, это означает, что у нее более нет данных для передачи, но что она все еще готова принимать данные от своего партнера. В этой схеме соединение разрывается, когда обе стороны выполняют операцию DISCONNECT.
Диаграмма состояний для установления и разрыва соединения показана на рис. 6.3. Каждый переход вызывается каким-то событием — операцией, выполненной локальным пользователем транспортного сервиса, или входящим пакетом. Для простоты мы будем считать, что каждый сегмент подтверждается отдельно. Мы также предполагаем, что используется модель симметричного разъединения, в которой клиент делает первый ход. Обратите внимание на наивность этой модели. Позднее, когда мы будем говорить о TCP, мы рассмотрим более реалистичные модели.
Рис. 6.3. Диаграмма состояний для простой схемы управления соединениями. Переходы, обозначенные курсивом, вызываются прибытием пакетов. Сплошными линиями показана последовательность состояний клиента. Пунктирными линиями показана последовательность состояний сервера
6.1.3. Сокеты Беркли
Теперь рассмотрим другой набор базовых операций транспортного уровня — базовые операции сокетов (иногда называемых гнездами), используемые для протокола TCP (Transmission Control Protocol — протокол управления передачей). Впервые сокеты стали применяться в 1983 году в операционной системе Berkeley UNIX 4.2BSD. Очень скоро они стали популярными и сейчас широко используются для интернет-программирования в большинстве операционных систем, особенно UNIX; кроме того, существует специальный API, предназначенный для программирования сокетов в системе Windows — «winsock».
Эти базовые операции приведены в табл. 6.2. Модель сокетов во многом подобна рассмотренной выше модели, но обладает большей гибкостью и предоставляет больше возможностей. Сегменты, соответствующие этой модели, будут рассматриваться ниже в этой главе.
Первые четыре базовых операции списка выполняются серверами в таком же порядке. Операция SOCKET создает новый сокет и выделяет для него место в таблице транспортной подсистемы. Параметры вызова указывают используемый формат адресов, тип требуемого сервиса (например, надежный поток байтов) и протокол. В случае успеха операция SOCKET возвращает обычный описатель файла, используемый при вызове следующих операций, подобно тому, как процедура OPEN работает для файла.
Таблица 6.2. Базовые операции сокетов для TCP
только что созданного сокета нет сетевых адресов. Они назначаются с помощью операции BIND. После того как сервер привязывает адрес к сокету, с ним могут связаться удаленные клиенты. Вызов SOCKET не создает адрес напрямую, так как некоторые процессы придают адресам большое значение (например, они использовали один и тот же адрес годами, и этот адрес всем известен), тогда как другим процессам это не важно.
Следом идет вызов LISTEN, который выделяет место для очереди входящих соединений на случай, если несколько клиентов попытаются соединиться одновременно. В отличие от операции LISTEN в нашем первом примере, операция LISTEN в модели сокетов не является блокирующим вызовом.
Чтобы заблокировать ожидание входящих соединений, сервер выполняет операцию ACCEPT. Получив сегмент с запросом соединения, транспортная подсистема создает новый сокет с теми же свойствами, что и у исходного сокета, и возвращает описатель файла для него. При этом сервер может разветвить процесс или поток, чтобы обработать соединение для нового сокета и вернуться к ожиданию следующего соединения для оригинального сокета.
Теперь посмотрим на этот процесс со стороны клиента. В этом случае также сначала с помощью операции SOCKET должен быть создан сокет, но операция BIND здесь не требуется, так как используемый адрес не имеет значения для сервера. CONNECT блокирует вызывающего и инициирует активный процесс соединения. Когда этот процесс завершается (то есть когда соответствующий сегмент, посланный сервером, получен), процесс клиента разблокируется, и соединение считается установленным. После этого обе стороны могут использовать SEND и RECIEVE для передачи и получения данных по полнодуплексному соединению. Могут также применяться стандартные UNIX-вызовы READ и WRITE, если нет нужды в использовании специальных свойств SEND и RECIEVE.
В модели сокетов используется симметричный разрыв соединения. Соединение разрывается, когда обе стороны выполняют операцию CLOSE.
Получив широкое распространение, сокеты де-факто стали стандартом абстрагирования транспортных сервисов для приложений. Часто сокет-API используется вместе с TCP-протоколом для предоставления сервиса с установлением соединения, который называется надежным потоком байтов (на деле это надежный битовый канал, о котором мы говорили выше). Этот API может сочетаться и с другими протоколами, но во всех случаях результат должен быть одинаковым для потребителя транспортных услуг.
Преимущество сокет-API состоит в том, что приложение может использовать его и для других транспортных сервисов. К примеру, с помощью сокетов можно реализовать транспортный сервис без установления соединения. В таком случае операция CONNECT будет задавать адрес удаленного узла, а SEND и RECEIVE — отправлять и получать дейтаграммы. (Иногда используется расширенный набор вызовов — например, операции SENDTO и RECEIVEFROM, позволяющие приложению не ограничиваться одним транспортным узлом.) Иногда сокеты используются с транспортными протоколами, в которых вместо байтового потока применяется поток сообщений и которые могут включать или не включать управление перегрузкой. К примеру, DCCP (Datagram Congestion Control Protocol — дейтаграммный протокол с управлением перегрузкой) является вариантом UDP, включающим управление перегрузкой (Rohler и др., 2006). Задачу выбора необходимого сервиса решают в первую очередь потребители транспортных услуг.