Программирование в стандарте POSIX

       

Функции для работы с сокетами


Работа с сокетами как оконечными точками при взаимодействии процессов начинается с их (сокетов) создания посредством функции socket() (см. пример 11.19). Она возвращает открытый файловый дескриптор, который может быть использован в последующих вызовах функций, оперирующих с сокетами. В том же листинге показано описание функции socketpair(), создающей пару сокетов с установленным между ними соединением.

#include <sys/socket.h> int socket (int af, int type, int protocol); int socketpair (int af, int type, int protocol, int sds [2]);

Листинг 11.19. Описание функций socket() и socketpair(). (html, txt)

Аргумент af задает адресное семейство создаваемого сокета, аргумент type - тип, аргумент protocol - конкретный протокол (0 обозначает неспецифицированный подразумеваемый протокол, пригодный для запрошенного типа). Напомним, что подходящие значения для аргументов af, type и protocol можно получить с помощью описанной ранее функции getaddrinfo().

Функция socketpair() по назначению аналогична pipe(), только организуется не безымянный канал, а пара соединенных, безымянных (не привязанных к каким-либо адресам), идентичных сокетов, открытые файловые дескрипторы которых помещаются в массив sds. Обычно она используется для адресного семейства AF_UNIX; поддержка для других семейств не гарантируется.

После того как посредством функции bind() (см. пример 11.20) создан сокет, идентифицируемый дескриптором sd, ему присваивают локальный адрес, заданный аргументом address (address_len - длина структуры sockaddr, на которую указывает address). Источником локальных адресов для сокетов может служить вышеупомянутая функция getaddrinfo().

#include <sys/socket.h> int bind (int sd, const struct sockaddr *address, socklen_t address_len);

Листинг 11.20. Описание функции bind(). (html, txt)

Опросить присвоенный локальный адрес (его иногда называют именем сокета) можно с помощью функции getsockname() (см. пример 11.21): она помещает его в структуру sockaddr, на которую указывает аргумент address, а длину адреса записывает по указателю address_len.


#include <sys/socket.h> int getsockname (int sd, struct sockaddr *restrict address, socklen_t *restrict address_len);

Листинг 11.21. Описание функции getsockname(). (html, txt)

Если сокет ориентирован на режим с установлением соединения (имеет тип SOCK_STREAM), то, воспользовавшись функцией listen() (см. пример 11.22), его следует пометить как готового принимать соединения ("слушающего").



#include <sys/socket.h> int listen (int sd, int backlog);

Листинг 11.22. Описание функции listen(). (html, txt)

Аргумент backlog сообщает операционной системе рекомендуемую длину очереди соединений, ожидающих обработки слушающим сокетом. Реализация должна поддерживать значения аргумента backlog вплоть до конфигурационной константы SOMAXCONN, определенной в

заголовочном файле <sys/socket.h>. ОС имеет право установить длину очереди меньше рекомендуемой. При неположительном значении backlog очередь будет иметь зависящую от реализации минимальную длину.

Прием соединений выполняет функция accept() (см. пример 11.23). Она выбирает первое соединение из очереди, ассоциированной с заданным дескриптором sd слушающим сокетом, создает новый сокет с теми же адресным семейством, типом и протоколом и возвращает в качестве результата его файловый дескриптор.

#include <sys/socket.h> int accept (int sd, struct sockaddr *restrict address, socklen_t *restrict address_len);

Листинг 11.23. Описание функции accept(). (html, txt)

Если значение аргумента address отлично от пустого указателя, то в структуру sockaddr, на которую указывает address, помещается адрес сокета, пытающегося установить соединение. По указателю address_len при обращении к функции accept() должна быть задана длина переданной структуры sockaddr, а на выходе туда записывается длина адреса партнера по взаимодействию.

Если очередь соединений, ожидающих обработки слушающим сокетом, пуста и для дескриптора sd не установлен флаг O_NONBLOCK, то вызвавший функцию accept() процесс (поток управления) блокируется до появления подобного соединения.При непустой очереди функция select() сообщит о готовности дескриптора sd к чтению.

Другая сторона, т. е. потенциальный партнер по взаимодействию, инициирует соединение с помощью функции connect() (см. пример 11.24). Аргументы address и address_len стандартным образом задают адрес сокета (как правило, слушающего), с которым необходимо установить соединение.

#include <sys/socket.h> int connect (int sd, const struct sockaddr *address, socklen_t address_len);

Листинг 11.24. Описание функции connect(). (html, txt)



При непустой очереди функция select() сообщит о готовности дескриптора sd к чтению.

Другая сторона, т. е. потенциальный партнер по взаимодействию, инициирует соединение с помощью функции connect() (см. пример 11.24). Аргументы address и address_len стандартным образом задают адрес сокета (как правило, слушающего), с которым необходимо установить соединение.

#include <sys/socket.h> int connect (int sd, const struct sockaddr *address, socklen_t address_len);

Листинг 11.24. Описание функции connect().

Если для сокета, заданного аргументом sd (запрашивающего установление соединения), еще не выполнена привязка к локальному адресу, функция connect() сама осуществит связывание со свободным локальным адресом (правда, лишь при условии, что адресное семейство сокета отлично от AF_UNIX).

Функция connect() ограничится фиксацией адреса сокета, взаимодействующего с заданным аргументом sd, если тип сокета не требует установления соединения. В частности, для сокетов типа SOCK_DGRAM таким способом можно специфицировать адреса отправляемых (с помощью функции send()) и принимаемых (посредством обращения к функции recv()) датаграмм.

Когда в качестве аргумента address передается пустой указатель, адрес взаимодействующего сокета сбрасывается.

Попытка установить соединение блокирует вызывающий процесс на неспецифицируемый промежуток времени (в случае наличия флага O_NONBLOCK). Если по истечении этого промежутка соединение установить не удалось, вызов connect(), равно как и попытка установления соединения, завершаются неудачей. Если ожидание прерывается обрабатываемым сигналом, вызов connect() завершается неудачей (переменной errno присваивается значение EINTR), но установление соединения продолжается и будет завершено асинхронно.

Если для дескриптора sd задан флаг O_NONBLOCK, а соединение не может быть установлено немедленно, то вызов connect() завершается неудачей со значением errno, равным EINPROGRESS, но установление соединения продолжается и будет завершено асинхронно.


Последующие обращения к функции connect() с тем же сокетом, выполненные до установления соединения, завершаются неудачей (EALREADY).

Сокет оказывается в неспецифицированном состоянии, если функция connect() завершается неудачей по другим причинам. Приложения, соответствующие стандарту POSIX-2001, должны закрыть файловый дескриптор sd и создать новый сокет для продолжения попыток установить соединение.

После асинхронного установления соединения функции select() и poll() сообщат, что файловый дескриптор sd готов к записи.

Функция poll(), позволяющая мультиплексировать ввод/вывод в пределах набора файловых дескрипторов, была описана нами выше. Имеющая сходную направленность (но входящая в обязательную часть стандарта POSIX-2001) функция select() и ее недавно введенный аналог pselect() представлены в пример 11.25.

#include <sys/select.h> int pselect (int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, const struct timespec *restrict timeout, const sigset_t *restrict sigmask); int select (int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout); void FD_CLR (int fd, fd_set *fdset); int FD_ISSET (int fd, fd_set *fdset); void FD_SET (int fd, fd_set *fdset); void FD_ZERO (fd_set *fdset);

Листинг 11.25. Описание функций семейства select*().

Если значение аргумента readfds (writefds, errorfds) функции pselect() отлично от NULL, оно ссылается на объект типа fd_set, который на входе специфицирует набор файловых дескрипторов, проверяемых на готовность к чтению (или к записи, или к обработке исключительных ситуаций), а на выходе указывает, какие из них успешно прошли проверку. Аргумент nfds задает границу проверяемых дескрипторов (они являются небольшими целыми числами): дескриптор подлежит проверке, если его значение находится в пределах от 0 до (nfds - 1) включительно.

Стандарт POSIX-2001 определяет тип fd_set как абстрактный. Для работы со значениями этого типа служат макросы FD_ZERO() (сделать набор пустым), FD_SET() (добавить дескриптор к набору), FD_CLR() (удалить дескриптор из набора), FD_ISSET() (проверить принадлежность дескриптора набору).


Значение именованной константы FD_SETSIZE равно максимально допустимому числу дескрипторов в наборе.

В качестве результата pselect() возвращается общее количество дескрипторов во всех трех наборах, успешно прошедших проверку.

При отсутствии готовых дескрипторов вызывающий процесс блокируется, пока таковые не появятся, или пока не истечет заданное аргументом timeout время ожидания, или пока ожидание не будет прервано сигналом. Пустой указатель в качестве значения timeout означает бесконечное ожидание, а нулевые значения полей структуры timespec - отсутствие блокировки. Реализация может накладывать ограничение на максимальное время ожидания, но оно не должно быть меньше тридцати одного дня.

Если значение аргумента sigmask отлично от пустого указателя, функция pselect() на входе заменяет маску сигналов процесса на заданную, а на выходе восстанавливает прежнюю маску.

Функция pselect() поддерживает обычные файлы, терминалы и псевдотерминалы, каналы и сокеты.

Функция select() эквивалентна pselect() со следующими оговорками.

  1. Для функции select() время ожидания задается в секундах и микросекундах в виде структуры типа timeval, а для pselect() - в секундах и наносекундах как аргумент типа struct timespec.
  2. У функции select() нет аргумента - маски сигналов, что эквивалентно пустому указателю в качестве значения аргумента sigmask функции pselect().
  3. В случае успешного завершения функция select() может модифицировать структуру, на которую указывает аргумент timeout.


С сокетами могут быть ассоциированы опции, влияющие на их функционирование. Значения этих опций можно опросить или изменить с помощью функций getsockopt() и setsockopt() (см. пример 11.26).

#include <sys/socket.h> int getsockopt (int sd, int level, int option_name, void *restrict option_value, socklen_t *restrict option_len); int setsockopt (int sd, int level, int option_name, const void *option_value, socklen_t option_len);

Листинг 11.26. Описание функций getsockopt() и setsockopt().

Опции задаются именованными константами (аргумент option_name), которые определены в заголовочном файле <sys/socket.h>.


Выделим среди них наиболее употребительные и разделим на несколько групп. К первой отнесем опции с целочисленными значениями, описывающими характеристики или состояние сокета.

SO_ERROR

Статус ошибок (после опроса очищается).

SO_TYPE

Тип сокета.

Ко второй группе отнесем булевы опции, представленные целочисленными значениями (0 означает ложь).

SO_DEBUG

Сообщает, записывается ли отладочная информация о работе сокета.

SO_ACCEPTCONN

Указывает, является ли сокет слушающим.

В третью группу включены опции с целочисленными значениями, определяющими количественные характеристики объектов, ассоциированных с сокетом.

SO_SNDBUF

Размер буфера для передаваемых данных (выходного буфера).

SO_RCVBUF

Размер входного буфера.

SO_RCVLOWATM

Минимальное число байт, обрабатываемых при вводе. Подразумеваемое значение равно единице. Большие значения могут вызвать блокировку принимающего процесса до поступления в сокет необходимого объема данных.

SO_SNDLOWAT

Минимальное число байт, обрабатываемых при выводе.

В четвертую группу входят опции со структурными значениями.

SO_LINGER

Определяет, блокировать ли процесс при закрытии дескриптора sd до передачи буферизованных данных, и если блокировать, то на какой срок. Значением опции является структура linger, определенная в заголовочном файле <sys/socket.h> и содержащая, согласно стандарту POSIX-2001, по крайней мере следующие поля.

int l_onoff; /* Признак, включена ли опция блокирования */ /* при закрытии */

int l_linger; /* Длительность блокирования в секундах */ SO_RCVTIMEO

Длительность ожидания поступления данных при вводе. Значение - упомянутая выше структура типа timeval. Подразумевая длительность равна нулю. Если в течение специфицированного промежутка времени новых данных не поступило, операция ввода вернет число байт, меньше запрошенного, или завершится ошибкой EAGAIN.

SO_SNDTIMEO

Длительность ожидания отправки данных при выводе.

Не все из перечисленных опций могут быть переустановлены функцией setsockopt().


Исключение по понятным причинам составляют SO_ERROR, SO_TYPE, SO_ACCEPTCONN.

Аргумент level задает протокольный уровень опции. Уровню сокетов соответствует значение SOL_SOCKET, уровню TCP - IPPROTO_TCP.

Функция getpeername() (см. пример 11.27), во многом аналогичная рассмотренной выше функции getsockname(), позволяет опросить еще одну характеристику - адрес (имя) сокета, с которым установлено соединение.

#include <sys/socket.h> int getpeername (int sd, struct sockaddr *restrict address, socklen_t *restrict address_len);

Листинг 11.27. Описание функции getpeername().

После привязки сокета к локальному адресу, а также установления, при необходимости, соединения и задания значений опций, можно приступать к отправке и/или приему данных через сокет. Для этого служат функции, описания которых показаны в пример 11.28.

#include <sys/socket.h> ssize_t recvfrom (int sd, void *restrict buffer, size_t length, int flags, struct sockaddr *restrict address, socklen_t *restrict address_len); ssize_t recv (int sd, void *buffer, size_t length, int flags); ssize_t recvmsg (int sd, struct msghdr *message, int flags); ssize_t sendto (int sd, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len); ssize_t send (int sd, const void *buffer, size_t length, int flags); ssize_t sendmsg (int sd, const struct msghdr *message, int flags);

Листинг 11.28. Описание функций обмена данными через сокет.

Функция recvfrom() позволяет прочитать данные (в рассматриваемом контексте называемые также сообщением) из сокета с дескриптором sd и поместить их в буфер buffer длины length. Обычно функцию recvfrom() применяют к сокетам, ориентированным на режим без установления соединения, поскольку она выдает исходный адрес в структуре типа sockaddr.

В число возможных составляющих значения аргумента flags входят следующие флаги.

MSG_PEEK

Не удалять прочитанные данные. Следующий вызов recvfrom() или другой функции ввода снова вернет их.

MSG_WAITALL



Для сокетов типа SOCK_STREAM флаг означает, что вызывающий процесс блокируется до получения всего запрошенного объема данных (а не до прихода первого сообщения).

MSG_OOB

Запрашиваются экстренные данные. Трактовка этого понятия зависит от протокола.

Как и положено функции ввода, в результате recvfrom() возвращает количество прочитанных и помещенных в буфер данных. Для сокетов, учитывающих границы сообщений (SOCK_RAW, SOCK_DGRAM, SOCK_SEQPACKET), за одно обращение читается все сообщение; если оно не помещается в буфер, лишние байты (в отсутствие флага MSG_PEEK) отбрасываются.

Формально можно считать, что функция recv() эквивалентна recvfrom() с нулевым значением аргумента address_len. Поскольку она не позволяет узнать исходный адрес, ее обычно используют для сокетов, установивших соединение. (Можно провести и еще одну аналогию: если пренебречь флагами, функция recv() эквивалентна read().)

По сравнению с recv(), более содержательным аналогом функции recvfrom() является recvmsg(). Отличия в данном случае носят скорее синтаксический характер и по сути сводятся к способу передачи входных значений и возврата результатов: для минимизации числа аргументов функция recvmsg() использует структуру типа msghdr, которая, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля.

void *msg_name; /* Указатель на буфер для адреса */ /* (исходного или целевого) */

socklen_t msg_namelen; /* Размер буфера для адреса */

struct iovec *msg_iov; /* Массив для разнесения/сборки сообщений */

int msg_iovlen; /* Число элементов в массиве */ /* для разнесения/сборки сообщений */

void *msg_control; /* Указатель на буфер */ /* для вспомогательных данных */

socklen_t msg_controllen; /* Размер буфера для вспомогательных данных */

int msg_flags; /* Флаги принятого сообщения */

Если для сокета, специфицированного дескриптором sd, соединение не установлено, в заданный указателем msg_name буфер длины msg_namelen помещается исходный адрес сообщения; если этого не нужно, указатель msg_name можно сделать пустым.


При установлении соединения оба поля игнорируются.

Массив msg_iov совместно с полем msg_iovlen задает набор буферов для размещения принимаемых данных. Структура типа iovec определяется в заголовочном файле <sys/uio.h> и содержит по крайней мере два поля.

void *iov_base; /* Адрес области памяти (буфера) */

size_t iov_len; /* Размер области памяти (в байтах) */

Заданные полем msg_iov области памяти по очереди заполняются поступающими данными, пока не будут размещены все принятые данные или не заполнятся все буфера.

Трактовка вспомогательных данных (поля msg_control и msg_controllen структуры типа msghdr) в стандарте POSIX-2001 весьма туманна. Описаны лишь средства доступа к ним. Мы, однако, не будем на этом останавливаться.

В случае успешного завершения функции recvmsg() в поле msg_flags могут быть установлены следующие флаги.

MSG_EOR

Достигнута граница записи (если это понятие поддерживается протоколом).

MSG_OOB

Получены экстренные данные.

MSG_TRUNC

Пришлось урезать обычные данные.

MSG_CTRUNC

Пришлось урезать управляющие данные.

Подобно тому, как write() составляет пару read(), функцию sendto() можно считать парной по отношению к recvfrom(). Она предназначена для отправки через сокет sd сообщения, заданного аргументами message и length, по адресу dest_addr длины dest_len. Если для сокета установлено соединение, целевой адрес dest_addr игнорируется.

В число возможных составляющих значения аргумента flags входят следующие флаги.

MSG_EOR

Обозначает границу записи (если это понятие поддерживается протоколом).

MSG_OOB

Отправляются экстренные данные.

Разумеется, успешное завершение функции sendto() не гарантирует доставку сообщения. Результат, равный -1, указывает только на локально выявляемые ошибки.

Функция send() - пара для recv() со всеми следующими из этого обстоятельства эквивалентностями и аналогиями.

Для функции sendmsg() структура типа msghdr, на которую указывает аргумент message, является входной (что показывает спецификатор const).В поле msg_name задается целевой адрес. Поле msg_flags игнорируется.

Для завершения операций приема и/или отправки данных через сокет служит функция shutdown() (см. пример 11.29).

#include <sys/socket.h> int shutdown (int sd, int how);

Листинг 11.29. Описание функции shutdown().

Значение аргумента how показывает, что именно завершается: SHUT_RD прекращает прием, SHUT_WR - отправку, SHUT_RDWR - и то, и другое.


Содержание раздела