В предыдущем разделе было
показано, каким образом
взаимодействуют между собой
процессы, протекающие на разных
машинах, при этом обращалось
внимание на то, что способы
реализации взаимодействия могут
быть различаться в зависимости от
используемых протоколов и сетевых
средств. Более того, эти способы не
всегда применимы для обслуживания
взаимодействия процессов,
выполняющихся на одной и той же
машине, поскольку в них
предполагается существование
обслуживающего (серверного)
процесса, который при выполнении
системных функций open или read будет
приостанавливаться драйвером. В
целях создания более универсальных
методов взаимодействия процессов
на основе использования
многоуровневых сетевых протоколов
для системы BSD был разработан
механизм, получивший название
"sockets" (гнезда) (см. [Berkeley 83]). В
данном разделе мы рассмотрим
некоторые аспекты применения гнезд
(на пользовательском уровне
представления). Рисунок 11.18. Модель с
использованием гнезд Структура ядра имеет три уровня:
гнезд, протоколов и устройств (Рисунок 11.18). Уровень
гнезд выполняет функции интерфейса
между обращениями к операционной
системе (системным функциям) и
средствами низких уровней, уровень
протоколов содержит модули,
обеспечивающие взаимодействие
процессов (на рисунке упомянуты
протоколы TCP и IP), а уровень
устройств содержит драйверы,
управляющие сетевыми устройствами.
Допустимые сочетания протоколов и
драйверов указываются при
построении системы (в секции
конфигурации); этот способ уступает
по гибкости вышеупомянутому
потоковому механизму. Процессы
взаимодействуют между собой по
схеме клиент-сервер: сервер ждет
сигнала от гнезда, находясь на
одном конце дуплексной линии связи,
а процессы-клиенты взаимодействуют
с сервером через гнездо,
находящееся на другом конце,
который может располагаться на
другой машине. Ядро обеспечивает
внутреннюю связь и передает данные
от клиента к серверу. Гнезда, обладающие одинаковыми
свойствами, например, опирающиеся
на общие соглашения по
идентификации и форматы адресов (в
протоколах), группируются в домены
(управляемые одним узлом). В системе
BSD 4.2 поддерживаются домены: "UNIX
system" - для взаимодействия
процессов внутри одной машины и
"Internet" (межсетевой) - для
взаимодействия через сеть с
помощью протокола DARPA (Управление
перспективных исследований и
разработок Министерства обороны
США) (см. [Postel 80] и [Postel 81]). Гнезда
бывают двух типов: виртуальный
канал (потоковое гнездо, если
пользоваться терминологией Беркли)
и дейтаграмма. Виртуальный канал
обеспечивает надежную доставку
данных с сохранением исходной
последовательности. Дейтаграммы не
гарантируют надежную доставку с
сохранением уникальности и
последовательности, но они более
экономны в смысле использования
ресурсов, поскольку для них не
требуются сложные установочные
операции; таким образом,
дейтаграммы полезны в отдельных
случаях взаимодействия. Для каждой
допустимой комбинации типа
домен-гнездо в системе
поддерживается умолчание на
используемый протокол. Так,
например, для домена "Internet"
услуги виртуального канала
выполняет протокол транспортной
связи (TCP), а функции дейтаграммы -
пользовательский дейтаграммный
протокол (UDP). Существует несколько системных
функций работы с гнездами. Функция
socket устанавливает оконечную точку
линии связи. Format обозначает домен ("UNIX
system" или "Internet"), type - тип
связи через гнездо (виртуальный
канал или дейтаграмма), а protocol - тип
протокола, управляющего
взаимодействием. Дескриптор гнезда
sd, возвращаемый функцией socket,
используется другими системными
функциями. Закрытие гнезд
выполняет функция close. Функция bind связывает дескриптор
гнезда с именем: где sd - дескриптор гнезда, address -
адрес структуры, определяющей
идентификатор, характерный для
данной комбинации домена и
протокола (в функции socket). Length -
длина структуры address; без этого
параметра ядро не знало бы, какова
длина структуры, поскольку для
разных доменов и протоколов она
может быть различной. Например, для
домена "UNIX system" структура
содержит имя файла.
Процессы-серверы связывают гнезда
с именами и объявляют о
состоявшемся присвоении имен
процессам-клиентам. С помощью системной функции connect
делается запрос на подключение к
существующему гнезду: Семантический смысл параметров
функции остается прежним (см.
функцию bind), но address указывает уже на
выходное гнездо, образующее
противоположный конец линии связи.
Оба гнезда должны использовать
одни и те же домен и протокол связи,
и тогда ядро удостоверит
правильность установки линии
связи. Если тип гнезда -
дейтаграмма, сообщаемый функцией
connect ядру адрес будет
использоваться в последующих
обращениях к функции send через
данное гнездо; в момент вызова
никаких соединений не
производится. Пока процесс-сервер готовится к
приему связи по виртуальному
каналу, ядру следует выстроить
поступающие запросы в очередь на
обслуживание. Максимальная длина
очереди задается с помощью
системной функции listen: где sd - дескриптор гнезда,
а qlength - максимально-допустимое
число запросов, ожидающих
обработки. Рисунок 11.19. Прием
вызова сервером Системная функция accept принимает
запросы на подключение,
поступающие на вход
процесса-сервера: где sd - дескриптор гнезда, address -
указатель на пользовательский
массив, в котором ядро возвращает
адрес подключаемого клиента, addrlen -
размер пользовательского массива.
По завершении выполнения функции
ядро записывает в переменную addrlen
размер пространства, фактически
занятого массивом. Функция
возвращает новый дескриптор гнезда
(nsd), отличный от дескриптора sd.
Процесс-сервер может продолжать
слежение за состоянием
объявленного гнезда, поддерживая
связь с клиентом по отдельному
каналу (Рисунок 11.19). Функции send и recv выполняют
передачу данных через подключенное
гнездо. Синтаксис вызова функции
send: где sd - дескриптор гнезда, msg -
указатель на посылаемые данные, length
размер данных, count - количество
фактически переданных байт.
Параметр flags может содержать
значение SOF_OOB (послать данные out-of-band
- "через таможню"), если
посылаемые данные не учитываются в
общем информационном обмене между
взаимодействующими процессами.
Программа удаленной регистрации,
например, может послать out-of-band
сообщение, имитирующее нажатие на
клавиатуре терминала клавиши
"delete". Синтаксис вызова
системной функции recv: где buf - массив для приема данных,
length - ожидаемый объем данных, count
количество байт, фактически
переданных пользовательской
программе. Флаги (flags) могут быть
установлены таким образом, что
поступившее сообщение после чтения
и анализа его содержимого не будет
удалено из очереди, или настроены
на получение данных out-of-band. В
дейтаграммных версиях указанных
функций, sendto и recvfrom, в качестве
дополнительных параметров
указываются адреса. После
выполнения подключения к гнездам
потокового типа процессы могут
вместо функций send и recv использовать
функции read и write. Таким образом,
согласовав тип протокола, серверы
могли бы порождать процессы,
работающие только с функциями read и
write, словно имеют дело с обычными
файлами. Функция shutdown закрывает гнездовую
связь: где mode указывает, какой из сторон
(посылающей, принимающей или обеим
вместе) отныне запрещено участие в
процессе передачи данных. Функция
сообщает используемому протоколу о
завершении сеанса сетевого
взаимодействия, оставляя, тем не
менее, дескрипторы гнезд в
неприкосновенности. Освобождается
дескриптор гнезда только в
результате выполнения функции close. Системная функция getsockname получает
имя гнездовой связи, установленной
ранее с помощью функции bind: Функции getsockopt и setsockopt получают и
устанавливают значения различных
связанных с гнездом параметров в
соответствии с типом домена и
протокола. Рассмотрим обслуживающую
программу, представленную на
Рисунке 11.20. Процесс создает в
домене "UNIX system" гнездо
потокового типа и присваивает ему
имя sockname. Затем с помощью функции
listen устанавливается длина очереди
поступающих сообщений и начинается
цикл ожидания поступления
запросов. Функция accept
приостанавливает свое выполнение
до тех пор, пока протоколом не будет
зарегистрирован запрос на
подключение к гнезду с означенным
именем; после этого функция
завершается, возвращая
поступившему запросу новый
дескриптор гнезда. Процесс-сервер
порождает потомка, через которого
будет поддерживаться связь с
процессом-клиентом; родитель и
потомок при этом закрывают свои
дескрипторы, чтобы они не
становились помехой для
коммуникационного траффика
другого процесса. Процесс-потомок
ведет разговор с клиентом и
завершается после выхода из
функции read. Процесс-сервер
возвращается к началу цикла и ждет
поступления следующего запроса на
подключение. Рисунок 11.20.
Процесс-сервер в домене "UNIX
system" Рисунок 11.21.
Процесс-клиент в домене "UNIX
system" На Рисунке 11.21 показан
пример процесса-клиента, ведущего
общение с сервером. Клиент создает
гнездо в том же домене, что и сервер,
и посылает запрос на подключение к
гнезду с именем sockname. В результате
подключения процесс-клиент
получает виртуальный канал связи с
сервером. В рассматриваемом
примере клиент передает одно
сообщение и завершается. Если сервер обслуживает процессы
в сети, указание о том, что гнездо
принадлежит домену "Internet",
можно сделать следующим образом: и связаться с сетевым адресом,
полученным от сервера. В системе BSD
имеются библиотечные функции,
выполняющие эти действия. Второй
параметр вызываемой клиентом
функции connect содержит адресную
информацию, необходимую для
идентификации машины в сети (или
адреса маршрутов посылки сообщений
через промежуточные машины), а
также дополнительную информацию,
идентифицирующую приемное гнездо
машины-адресата. Если серверу нужно
одновременно следить за состоянием
сети и выполнением локальных
процессов, он использует два гнезда
и с помощью функции select определяет,
с каким клиентом устанавливается
связь в данный момент. Предыдущая
глава || Оглавление
|| Следующая глава
11.4 ГНЕЗДА
sd = socket(format,type,protocol);
bind(sd,address,length);
connect(sd,address,length);
listen(sd,qlength)
nsd = accept(sd,address,addrlen);
count = send(sd,msg,length,flags);
count = recv(sd,buf,length,flags);
shutdown(sd,mode)
getsockname(sd,name,length);
#include <sys/types.h>
#include <sys/socket.h>
main()
{
int sd,ns;
char buf[256];
struct sockaddr sockaddr;
int fromlen;
sd = socket(AF_UNIX,SOCK_STREAM,0);
/* имя гнезда - не может включать пустой символ */
bind(sd,"sockname",sizeof("sockname") - 1);
listen(sd,1);
for (;;)
{
ns = accept(sd,&sockaddr,&fromlen);
if (fork() == 0)
{
/* потомок */
close(sd);
read(ns,buf,sizeof(buf));
printf("сервер читает '%s'\n",buf);
exit();
}
close(ns);
}
}
#include <sys/types.h>
#include <sys/socket.h>
main()
{
int sd,ns;
char buf[256];
struct sockaddr sockaddr;
int fromlen;
sd = socket(AF_UNIX,SOCK_STREAM,0);
/* имя в запросе на подключение не может включать
/* пустой символ */
if (connect(sd,"sockname",sizeof("sockname") - 1) == -1)
exit();
write(sd,"hi guy",6);
}
socket(AF_INET,SOCK_STREAM,0);