Операционные системы 2011 (1114689), страница 50
Текст из файла (страница 50)
Клиент посылает серверу запросы напредоставление услуги, а сервер отвечает на эти запросы.Схема использования механизма сокетов для взаимодействия в рамках модели«клиент-сервер» такова. Процесс-сервер запрашивает у ОС сокет и, получив его,присваивает ему некоторое имя (адрес), которое предполагается заранее известным всемклиентам, которые захотят общаться с данным сервером. После этого сервер переходит врежим ожидания и обработки запросов от клиентов.
Клиент, со своей стороны, тожесоздает сокет и запрашивает соединение своего сокета с сокетом сервера, имеющимизвестное ему имя (адрес). После того, как соединение будет установлено, клиент и сервермогут обмениваться данными через соединенную пару сокетов. Ниже мы подробнорассмотрим функции, выполняющие все необходимые действия с сокетами, и напишемпример небольшой серверной и клиентской программы, использующих сокеты.3.3.1 Типы сокетов. Коммуникационный доменСокеты подразделяются на несколько типов в зависимости от типакоммуникационного соединения, который они используют.
Два основных типа183коммуникационных соединений и, соответственно, сокетов представляет собойсоединение с использованием виртуального канала и датаграммное соединение.Соединение с использованием виртуального канала – это последовательный потокбайтов, гарантирующий надежную доставку сообщений с сохранением порядка ихследования.
Данные начинают передаваться только после того, как виртуальный каналустановлен, и канал не разрывается, пока все данные не будут переданы. Примеромсоединения с установлением виртуального канала является механизм каналов в UNIX,аналогом такого соединения из реальной жизни также является телефонный разговор.Заметим, что границы сообщений при таком виде соединений не сохраняются, т.е.приложение, получающее данные, должно само определять, где заканчивается односообщение и начинается следующее. Такой тип соединения может также поддерживатьпередачу экстренных сообщений вне основного потока данных, если это возможно прииспользовании конкретного выбранного протокола.Датаграммное соединение используется для передачи отдельных пакетов,содержащих порции данных – датаграмм.
Для датаграмм не гарантируется доставка в томже порядке, в каком они были посланы. Вообще говоря, для них не гарантируетсядоставка вообще, надежность соединения в этом случае ниже, чем при установлениивиртуального канала. Однако датаграммные соединения, как правило, более быстрые.Примером датаграммного соединения из реальной жизни может служить обычная почта:письма и посылки могут приходить адресату не в том порядке, в каком они были посланы,а некоторые из них могут и совсем пропадать.Поскольку сокеты могут использоваться как для локального, так и для удаленноговзаимодействия, встает вопрос о пространстве адресов сокетов.
При создании сокетауказывается так называемый коммуникационный домен, к которому данный сокет будетпринадлежать. Коммуникационный домен определяет конкретную модель именования(форматы адресов, правила их интерпретации), а также область взаимодействияпроцессов. Мы будем рассматривать два основных домена: для локальноговзаимодействия – домен AF_UNIX и для взаимодействия в рамках сети – домен AF_INET(префикс AF обозначает сокращение от «address family» – семейство адресов). В доменеAF_UNIX формат адреса – это допустимое имя файла, в домене AF_INET адрес образуютимя хоста + номер порта (порт – виртуальная точка соединения, которая позволяетадресовать конкретный процесс извне).Заметим, что фактически коммуникационный домен определяет такжеиспользуемые семейства протоколов.
Так, для домена AF_UNIX это будут внутренниепротоколы ОС, для домена AF_INET – протоколы семейства TCP/IP. Современныесистемы поддерживают и другие коммуникационные домены, например BSD UNIXподдерживает также третий домен – AF_NS, использующий протоколы удаленноговзаимодействия Xerox NS.Ниже приведен набор функций для работы с сокетами.3.3.2 Создание и конфигурирование сокета3.3.2.1 Создание сокета#include <sys/types.h>#include <sys/socket.h>int socket (int domain, int type, int protocol)Функция создания сокета так и называется – socket(). У нее имеется триаргумента.
Первый аргумент – domain – обозначает коммуникационный домен, ккоторому должен принадлежать создаваемый сокет. Для двух рассмотренных намидоменов соответствующие константы будут равны, как мы уже говорили, AF_UNIX иAF_INET. Второй аргумент – type – определяет тип соединения, которым будет184пользоваться сокет (и, соответственно, тип сокета). Для двух основных рассматриваемыхнами типов сокетов это будут константы SOCK_STREAM для соединения с установлениемвиртуального канала и SOCK_DGRAM для датаграмм6.
Третий аргумент – protocol –задает конкретный протокол, который будет использоваться в рамках данногокоммуникационного домена для создания соединения. Если установить значение данногоаргумента в 0, система автоматически выберет подходящий протокол. В наших примерахмы так и будем поступать. Однако здесь для справки приведем константы для протоколов,используемых в домене AF_INET:IPPROTO_TCP – обозначает протокол TCP (корректно при создании сокета типаSOCK_STREAM)IPPROTO_UDP – обозначает протокол UDP (корректно при создании сокета типаSOCK_DGRAM)Функция socket() возвращает в случае успеха положительное целое число –дескриптор сокета, которое может быть использовано в дальнейших вызовах при работе сданным сокетом.
Заметим, что дескриптор сокета фактически представляет собойфайловый дескриптор, а именно, он является индексом в таблице файловых дескрипторовпроцесса, и может использоваться в дальнейшем для операций чтения и записи в сокет,которые осуществляются подобно операциям чтения и записи в файл (подробно этиоперации будут рассмотрены ниже).В случае если создание сокета с указанными параметрами невозможно (например,при некорректном сочетании коммуникационного домена, типа сокета и протокола),функция возвращает –1.3.3.2.2 СвязываниеДля того чтобы к созданному сокету мог обратиться какой-либо процесс извне,необходимо связать с этим сокетом некоторое имя (адрес). Как мы уже говорили, форматадреса зависит от коммуникационного домена, в рамках которого действует сокет, иможет представлять собой либо путь к файлу, либо сочетание IP-адреса и номера порта.Но в любом случае связывание сокета с конкретным адресом осуществляется одной и тойже функцией bind:#include <sys/types.h>#include <sys/socket.h>int bind (int sockfd, struct sockaddr *myaddr, int addrlen)Первый аргумент функции – дескриптор сокета, возвращенный функциейsocket(); второй аргумент – указатель на структуру, содержащую адрес сокета.
Длядомена AF_UNIX формат структуры описан в <sys/un.h> и выглядит следующимобразом:#include <sys/un.h>struct sockaddr_un {short sun_family; /* == AF_UNIX */char sun_path[108];};Для домена AF_INET формат структуры описан в <netinet/in.h> и выглядитследующим образом:#include <netinet/in.h>6Заметим, что данный аргумент может принимать не только указанные два значения, например, типсокета SOCK_SEQPACKET обозначает соединение с установлением виртуального канала со всемивытекающими отсюда свойствами, но при этом сохраняются границы сообщений; однако данный типсокетов не поддерживается ни в домене AF_UNIX, ни в домене AF_INET, поэтому мы его здесьрассматривать не будем185struct sockaddr_in {short sin_family; /* == AF_INET */u_short sin_port; /* port number */struct in_addr sin_addr; /* host IP address */char sin_zero[8]; /* not used */};Последний аргумент функции задает реальный размер структуры, на которуюуказывает myaddr.Важно отметить, что если мы имеем дело с доменом AF_UNIX и адрес сокетапредставляет собой имя файла, то при выполнении функции bind() система в качествепобочного эффекта создает файл с таким именем.
Поэтому для успешного выполненияbind() необходимо, чтобы такого файла не существовало к данному моменту. Этоследует учитывать, если мы «зашиваем» в программу определенное имя и намеренызапускать нашу программу несколько раз на одной и той же машине – в этом случае дляуспешной работы bind() необходимо удалять файл с этим именем перед связыванием.Кроме того, в процессе создания файла, естественно, проверяются права доступапользователя, от имени которого производится вызов, ко всем директориям,фигурирующим в полном путевом имени файла, что тоже необходимо учитывать призадании имени.
Если права доступа к одной из директорий недостаточны, вызов bind()завершится неуспешно.В случае успешного связывания bind() возвращает 0, в случае ошибки – -1.3.3.3 Предварительное установление соединения.3.3.3.1 Сокеты с установлением соединения. Запрос на соединение.Различают сокеты с предварительным установлением соединения, когда доначала передачи данных устанавливаются адреса сокетов отправителя и получателяданных – такие сокеты соединяются друг с другом и остаются соединенными доокончания обмена данными; и сокеты без установления соединения, когда соединениедо начала передачи данных не устанавливается, а адреса сокетов отправителя иполучателя передаются с каждым сообщением.
Если тип сокета – виртуальный канал, тосокет должен устанавливать соединение, если же тип сокета – датаграмма, то, какправило, это сокет без установления соединения, хотя последнее не является требованием.Для установления соединения служит следующая функция:#include <sys/types.h>#include <sys/socket.h>int connect (int sockfd, struct sockaddr *serv_addr,int addrlen);Здесь первый аргумент – дескриптор сокета, второй аргумент – указатель наструктуру, содержащую адрес сокета, с которым производится соединение, в формате,который мы обсуждали выше, и третий аргумент содержит реальную длину этойструктуры. Функция возвращает 0 в случае успеха и –1 в случае неудачи, при этом кодошибки можно посмотреть в переменной errno.Заметим, что в рамках модели «клиент-сервер» клиенту, вообще говоря, не важно,какой адрес будет назначен его сокету, так как никакой процесс не будет пытатьсянепосредственно установить соединение с сокетом клиента.
Поэтому клиент может невызывать предварительно функцию bind(), в этом случае при вызове connect()система автоматически выберет приемлемые значения для локального адреса клиента.Однако сказанное справедливо только для взаимодействия в рамках домена AF_INET, вдомене AF_UNIX клиентское приложение само должно позаботиться о связывании сокета.1863.3.3.2 Сервер: прослушивание сокета и подтверждение соединения.Следующие два вызова используются сервером только в том случае, еслииспользуются сокеты с предварительным установлением соединения.#include <sys/types.h>#include <sys/socket.h>int listen (int sockfd, int backlog);Этот вызов используется процессом-сервером для того, чтобы сообщить системе отом, что он готов к обработке запросов на соединение, поступающих на данный сокет. Дотех пор, пока процесс – владелец сокета не вызовет listen(), все запросы насоединение с данным сокетом будут возвращать ошибку.