Н.В. Вдовикина, И.В. Машечкин, А.Н. Терехин, А.Н. Томилин - Операционные системы - взаимодействие процессов (2008) (1114653), страница 26
Текст из файла (страница 26)
Еслиустановить значение данного аргумента в 0, система автоматическивыберет подходящий протокол. В наших примерах мы так и будемпоступать. Однако здесь для справки приведем константы дляпротоколов, используемых в домене AF_INET:IPPROTO_TCP – обозначает протокол TCP (корректно присоздании сокета типа SOCK_STREAM);IPPROTO_UDP – обозначает протокол UDP (корректно присоздании сокета типа SOCK_DGRAM).Функция socket() возвращаетв случае успехаположительное целое число – дескриптор сокета, которое можетбыть использовано в дальнейших вызовах при работе с даннымсокетом. Заметим, что дескриптор сокета фактически представляетсобой файловый дескриптор, а именно, он является индексом втаблице файловых дескрипторов процесса, и может использоваться вдальнейшем для операций чтения и записи в сокет, которыеосуществляются подобно операциям чтения и записи в файл(подробно эти операции будут рассмотрены ниже).В случае если создание сокета с указанными параметраминевозможно(например,принекорректномсочетаниикоммуникационного домена, типа сокета и протокола), функциявозвращает –1.Связывание сокета с адресомДля того чтобы к созданному сокету мог обратиться какойлибо процесс извне, необходимо присвоить ему адрес.
Как мы ужеговорили, формат адреса зависит от коммуникационного домена, врамках которого действует сокет, и может представлять собой либопуть к файлу, либо сочетание IP-адреса и номера порта. Но в любомслучае связывание сокета с конкретным адресом осуществляетсяодной и той же функцией bind:#include <sys/types.h>#include <sys/socket.h>int bind (int sockfd, struct sockaddr *myaddr, intaddrlen);Первыйаргументфункции–дескрипторсокета,возвращенный функцией socket(); второй аргумент – указатель наструктуру, содержащую адрес сокета.
Для домена AF_UNIX форматструктуры описан в <sys/un.h> и выглядит следующим образом:#include <sys/un.h>160struct sockaddr_un {short sun_family; /* == AF_UNIX */char sun_path[108];};Длядомена AF_INET формат структуры<netinet/in.h> и выглядит следующим образом:описанв#include <netinet/in.h>struct sockaddr_in {short sin_family; /* == AF_INET */u_short sin_port;/* port number */struct in_addr sin_addr;char sin_zero[8];/* host IP address *//* not used */};Последний аргумент функции задаетструктуры, на которую указывает myaddr.реальныйразмерВажно отметить, что если мы имеем дело с доменом AF_UNIX иадрес сокета представляет собой имя файла, то при выполнениифункции bind() система в качестве побочного эффекта создаетфайл с таким именем.
Поэтому для успешного выполнения bind()необходимо, чтобы такого файла не существовало к данномумоменту. Это следует учитывать, если мы «зашиваем» в программуопределенное имя и намерены запускать нашу программу несколькораз на одной и той же машине – в этом случае для успешной работыbind() необходимо удалять файл с этим именем перед связыванием.Кроме того, в процессе создания файла, естественно, проверяютсяправа доступа пользователя, от имени которого производится вызов,ко всем директориям, фигурирующим в полном путевом именифайла, что тоже необходимо учитывать при задании имени. Еслиправа доступа к одной из директорий недостаточны, вызов bind()завершится неуспешно.В случае успешного связывания bind() возвращает 0, в случаеошибки – -1.7.1.4 Предварительное установление соединенияСокеты с установлением соединения.
Запрос на соединениеРазличают сокеты с предварительным установлениемсоединения, когда до начала передачи данных устанавливаютсяадреса сокетов отправителя и получателя данных – такие сокетысоединяются друг с другом и остаются соединенными до окончания161обмена данными; и сокеты без установления соединения, когдасоединение до начала передачи данных не устанавливается, а адресасокетов отправителя и получателя передаются с каждымсообщением. Если тип сокета – виртуальный канал, то сокет долженустанавливать соединение, если же тип сокета – датаграмма, то, какправило, это сокет без установления соединения (хотя в отдельныхслучаях для удобства программист может установить соединение –тогда он сможет использовать менее громоздкие вызовы приемапередачи, не указывая в них всякий раз структуры адреса). Дляустановления соединения служит следующая функция:#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 клиентское приложение должно явно позаботиться освязывании сокета.Сервер: прослушивание сокета и подтверждение соединенияСледующие два вызова используются сервером только в томслучае,еслииспользуютсясокетыспредварительнымустановлением соединения.#include <sys/types.h>#include <sys/socket.h>int listen (int sockfd, int backlog);Этот вызов используется процессом-сервером для того, чтобысообщить системе о том, что он готов к обработке запросов насоединение, поступающих на данный сокет. Тем самым сервер162входит в режим прослушивания сокета.
До тех пор, пока процесс –владелец сокета не вызовет listen(), все запросы на соединение сданным сокетом будут возвращать ошибку.Первый аргумент функции – дескриптор сокета. Второйаргумент, backlog, содержит максимальный размер очередизапросов на соединение. ОС буферизует приходящие запросы насоединение, выстраивая их в очередь до тех пор, пока процесс несможет их обработать. В случае если очередь запросов насоединение переполняется, поведение ОС зависит от того, какойпротокол используется для соединения. Если конкретный протоколсоединениянеподдерживаетвозможностьперепосылки(retransmission) данных, то соответствующий вызов connect()вернетошибкуECONNREFUSED.Еслижеперепосылкаподдерживается (как, например, при использовании TCP), ОСпросто выбрасывает пакет, содержащий запрос на соединение, какесли бы она его не получала вовсе.
При этом пакет будетприсылаться повторно до тех пор, пока очередь запросов неуменьшится и попытка соединения не увенчается успехом, либопока не произойдет тайм-аут, определенный для протокола. Впоследнем случае вызов connect() завершится с ошибкойETIMEDOUT. Это позволит клиенту отличить, был ли процесс-серверслишком занят, либо он не функционировал.
В большинстве системмаксимальный допустимый размер очереди равен 5.Конкретное соединение устанавливается при помощи вызоваaccept():#include <sys/types.h>#include <sys/socket.h>int accept (int sockfd, struct sockaddr *addr, int*addrlen);Этот вызов применяется сервером для удовлетворенияпоступившего клиентского запроса на соединение с сокетом,который сервер к тому моменту уже прослушивает (т.е.предварительно была вызвана функция listen()). Вызов accept()извлекает первый запрос из очереди запросов, ожидающихсоединения, и устанавливает с ним соединение.
Если к моментувызова accept() очередь запросов на соединение пуста, процесс,вызвавший accept(), блокируется до поступления запросов.Когда запрос поступает и соединение устанавливается,accept() создает новый сокет, который будет использоваться дляработы с данным соединением, и возвращает дескриптор этого163нового сокета, соединенного с сокетом клиентского процесса.
Приэтом первоначальный сокет продолжает оставаться в состояниипрослушивания. Через новый сокет осуществляется обмен данными,в то время как старый сокет продолжает обрабатывать другиепоступающие запросы на соединение (напомним, что именнопервоначально созданный сокет связан с адресом, известнымклиентам, поэтому все клиенты могут слать запросы только насоединение с этим сокетом). Это позволяет процессу-серверуподдерживать несколько соединений одновременно. Обычно этореализуется путем порождения для каждого установленногосоединения отдельного процесса-потомка, который занимаетсясобственно обменом данными только с этим конкретным клиентом,в то время как процесс-родитель продолжает прослушиватьпервоначальный сокет и порождать новые соединения (см. Пример31).Во втором параметре передается указатель на структуру, вкоторой возвращается адрес клиентского сокета, с которымустановлено соединение, а в третьем параметре возвращаетсяреальная длина этой структуры.
Благодаря этому сервер всегдазнает, куда ему в случае надобности следует послать ответноесообщение. Если адрес клиента нас не интересует, в качествевторого аргумента можно передать NULL.7.1.5 Прием и передача данныхСобственно для приема и передачи данных через сокетиспользуются три пары функций.#include <sys/types.h>#include <sys/socket.h>int send(int sockfd, const void*msg, int len, unsigned int flags);int recv(int sockfd, void *buf, intlen, unsigned int flags);Эти функции используются для обмена только через сокет спредварительно установленным соединением. Аргументы функцииsend(): sockfd – дескриптор сокета, через который передаютсяданные, msg и len – сообщение и его длина.