Основы программирования в Linux - Нейл Мэтью
Шрифт:
Интервал:
Закладка:
К наиболее популярным доменам сокетов относятся AF_UNIX, применяемый для локальных сокетов, реализуемых средствами файловых систем UNIX и Linux, и AF_INET, используемый для сетевых сокетов UNIX. Сокеты домена AF_INET могут применяться программами, взаимодействующими в сетях на базе протоколов TCP/IP, включая Интернет. Интерфейс ОС Windows Winsock также предоставляет доступ к этому домену сокетов.
Параметр сокета type задает характеристики обмена данными, применяемые для нового сокета. Возможными значениями могут быть SOCK_STREAM и SOCK_DGRAM.
□ SOCK_STREAM — это упорядоченный, надежный, основанный на соединении, двунаправленный поток байтов. В случае домена сокетов AF_INET этот тип обмена данными по умолчанию обеспечивается TCP-соединением, которое устанавливается между двумя конечными точками потоковых сокетов при подключении. Данные могут передаваться в двух направлениях по линии связи сокетов. Протоколы TCP включают в себя средства фрагментации и последующей повторной сборки сообщений больших объемов и повторной передачи любых их частей, которые могли быть потеряны в сети.
□ SOCK_DGRAM — дейтаграммный сервис. Вы можете использовать такой сокет для отправки сообщений с фиксированным (обычно небольшим) максимальным объемом, но при этом нет гарантии, что сообщение будет доставлено или что сообщения не будут переупорядочены в сети. В случае сокетов домена AF_INET этот тип передачи данных обеспечивается дейтаграммами UDP (User Datagram Protocol, пользовательский протокол дейтаграмм).
Протокол, применяемый для обмена данными, обычно определяется типом сокета и доменом. Как правило, выбора нет. Параметр protocol применяется в тех случаях, когда выбор все же предоставляется. Задание 0 позволяет выбрать стандартный протокол, используемый во всех примерах данной главы.
Системный вызов socket возвращает дескриптор, во многом похожий на низкоуровневый файловый дескриптор. Когда сокет подключен к концевой точке другого сокета, для отправки и получения данных с помощью сокетов можно применять системные вызовы read и write с дескриптором сокета. Системный вызов close используется для удаления сокетного соединения.
Адреса сокетов
Каждый домен сокетов требует своего формата адресов. В домене AF_UNIX адрес описывается структурой sockaddr_un, объявленной в заголовочном файле sys/un.h:
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[]; /* Путь к файлу */
};
Для того чтобы адреса разных типов могли передаваться в системные вызовы для обработки сокетов, все адресные форматы описываются похожей структурой, которая начинается с поля (в данном случае sun_family), задающего тип адреса (домен сокета). В домене AF_UNIX адрес задается именем файла в поле структуры sun_path.
В современных системах Linux тип sa_family_t, описанный в стандарте X/Open как объявляемый в заголовочном файле sys/un.h, интерпретируется как тип short. Кроме того, размер pathname, задаваемого в поле sun_path, ограничен (в Linux указывается 108 символов; в других системах может применяться именованная константа, например, UNIX_MAX_PATH). Поскольку размер адресной структуры может меняться, многие системные вызовы сокетов требуют или предоставляют на выходе длину, которая будет использоваться для копирования конкретной адресной структуры.
В домене AF_INET адрес задается с помощью структуры с именем sockaddr_in, определенной в файле netinet/in.h, которая содержит как минимум следующие элементы:
struct sockaddr_in {
short int sin_family; /* AF_INET */
unsigned short int sin_port; /* Номер порта */
struct in_addr sin_addr; /* Интернет-адрес */
};
Структура IP-адреса типа in_addr определена следующим образом:
struct in_addr {
unsigned long int s_addr;
};
Четыре байта IP-адреса образуют одно 32-разрядное значение. Сокет домена AF_INET полностью описывается IP-адресом и номером порта. С точки зрения приложения все сокеты действуют как файловые дескрипторы, и их адреса задаются уникальными целочисленными значениями.
Именование сокета
Для того чтобы сделать сокет (созданный с помощью вызова socket) доступным для других процессов, серверная программа должна присвоить сокету имя. Сокеты домена AF_UNIX связаны с полным именем файла в файловой системе, как вы видели в программе-примере server1. Сокеты домена AF_INET связаны с номером IP-порта.
#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address, size_t address len);
Системный вызов bind присваивает адрес, заданный в параметре address, неименованному сокету, связанному с дескриптором сокета socket. Длина адресной структуры передается в параметре address_len:
Длина и формат адреса зависят от адресного семейства. В системном вызове bind указатель конкретной адресной структуры должен быть приведен к обобщенному адресному типу (struct sockaddr*).
В случае успешного завершения bind возвращает 0. Если он завершается аварийно, возвращается -1, и переменной errno присваивается одно из значений, перечисленных в табл. 15.2.
Таблица 15.2
Значение errno Описание EBADF Неверный файловый дескриптор ENOTSOCK Файловый дескриптор не ссылается на сокет EINVAL Файловый дескриптор ссылается на сокет, уже получивший имя EADDRNOTAVAIL Недопустимый адрес EADDINUSE У адреса уже есть связанный с ним сокет Для сокетов домена AF_UNIX есть несколько дополнительных значений EACCESS Невозможно создать имя в файловой системе из-за прав доступа ENOTDIR, ENAMETOOLONG Означает недопустимое имя файлаСоздание очереди сокетов
Для приема запросов на входящие соединения на базе сокетов серверная программа должна создать очередь для хранения ждущих обработки запросов. Формируется она с помощью системного вызова listen.
#include <sys/socket.h>
int listen(int socket, int backlog);
Система Linux может ограничить количество ждущих обработки соединений, которые могут храниться в очереди. В соответствии с этим максимумом вызов listen задает длину очереди, равной backlog. Входящие соединения, не превышающие максимальной длины очереди, сохраняются в ожидании сокета; последующим запросам на соединение будет отказано, и клиентская попытка соединения завершится аварийно. Этот механизм реализуется вызовом listen для того, чтобы можно было сохранить ждущие соединения запросы, пока серверная программа занята обработкой запроса предыдущего клиента. Очень часто параметр backlog равен 5.
Функция listen вернет 0 в случае успешного завершения и -1 в случае ошибки. Как и для системного вызова bind, ошибки могут обозначаться константами EBADF, EINVAL И ENOTSOCK.
Прием запросов на соединение
После создания и именования сокета серверная программа может ждать запросы на выполнение соединения с сокетом с помощью системного вызова accept:
#include <sys/socket.h>
int accept(int socket, struct sockaddr *address, size_t *address_len);
Системный вызов accept возвращает управление, когда клиентская программа пытается подключиться к сокету, заданному в параметре socket. Этот клиент — первый из ждущих соединения в очереди данного сокета. Функция accept создает новый сокет для обмена данными с клиентом и возвращает его дескриптор. У нового сокета будет тот же тип, что и у сокета сервера, ждущего запросы на соединения.
Предварительно сокету должно быть присвоено имя с помощью системного вызова bind и у него должна быть очередь запросов на соединение, место для которой выделил системный вызов listen. Адрес вызывающего клиента будет помещен в структуру sockaddr, на которую указывает параметр address. Если адрес клиента не представляет интереса, в этом параметре может задать пустой указатель.
Параметр address_len задает длину адресной структуры клиента. Если адрес клиента длиннее, чем это значение, он будет урезан. Перед вызовом accept в параметре address_len должна быть задана ожидаемая длина адреса. По возвращении из вызова в address_len будет установлена реальная длина адресной структуры запрашивающего соединение клиента.