UNIX: разработка сетевых приложений
Как мы уже говорили, вызов функции
позволяет нам задать IP-адрес и порт (вместе или по отдельности) либо не задавать никаких аргументов. В табл. 4.5 приведены все возможные значения, которые присваиваются аргументамbindиsin_addrлибоsin_portиsin6_addrв зависимости от желаемого результата.sin6_portТаблица 4.5. Результаты задания IP-адреса и (или) номера порта в функции bind
Процесс задает Результат IP-адрес Порт Универсальный 0 Ядро выбирает IP-адрес и порт Универсальный Ненулевое значение Ядро выбирает IP-адрес, процесс задает порт Локальный 0 Процесс задает IP-адрес, ядро выбирает порт Локальный Ненулевое значение Процесс задает IP-адрес и порт Если мы зададим нулевой номер порта, то при вызове функции
ядро выберет динамически назначаемый порт. Но если мы зададим IP-адрес с помощью символов подстановки, ядро не выберет локальный IP-адрес, пока к сокету не присоединится клиент (TCP) либо на сокет не будет отправлена дейтаграмма (UDP).bindВ случае IPv4 универсальный адрес, состоящий из символов подстановки (wildcard), задается константой
, значение которой обычно нулевое. Это указывает ядру на необходимость выбора IP-адреса. Пример вы видели в листинге 1.5:INADDR_ANYstruct sockaddr_in servaddr;servaddr sin_addr s_addr = htonl(INADDR_ANY); /* универсальный */Этот прием работает с IPv4, где IP-адрес является 32-разрядным значением, которое можно представить как простую численную константу (в данном случае 0), но воспользоваться им при работе с IPv6 мы не можем, поскольку 128-разрядный адрес IPv6 хранится в структуре. (В языке С мы не можем поместить структуру в правой части оператора присваивания.) Эта проблема решается следующим образом:
struct sockaddr_in6 serv;serv sin6_addr = in6addr_any; /* универсальный */Система выделяет место в памяти и инициализирует переменную
, присваивая ей значение константыin6addr_any. Объявление внешней константыIN6ADDR_ANY_INITсодержится в заголовочном файлеin6addr_any.<netinet/in.h>Значение
(0) не зависит от порядка байтов, поэтому использование функцииINADDR_ANYв действительности не требуется. Но поскольку все константыhtonl, определенные в заголовочном файлеINADDR_, задаются в порядке байтов узла, с любой из этих констант следует использовать функцию<netinet/in.h>.htonlЕсли мы поручаем ядру выбрать для нашего сокета номер динамически назначаемого порта, то функция
не возвращает выбранное значение. В самом деле, она не может возвратить это значение, поскольку второй аргумент функцииbindимеет спецификаторbind. Чтобы получить значение динамически назначаемого порта, заданного ядром, потребуется вызвать функциюconst, которая возвращает локальный адрес протокола.getsocknameТипичным примером процесса, связывающего с сокетом конкретный IP-адрес, служит узел, на котором работают веб-серверы нескольких организаций (см. раздел 14.2 [112]). Прежде всего, у каждой организации есть свое собственное доменное имя, например
. Доменному имени каждой организации сопоставляется некоторый IP-адрес; различным организациям сопоставляются различные адреса, но обычно из одной и той же подсети. Например, если маска подсети 198.69.10, то IP-адресом первой организации может быть 198. 69.10.128, следующей — 198.69.10.129, и т.д. Все эти IP-адреса затем становятся псевдонимами, или альтернативными именами (alias), одного сетевого интерфейса (например, при использовании параметраwww.organization.comкомандыaliasв 4.4BSD). В результате уровень IP будет принимать входящие дейтаграммы, предназначенные для любого из адресов, являющихся псевдонимами. Наконец, для каждой организации запускается по одной копии сервера HTTP, и каждая копия связывается с помощью функцииifconfigтолько с IP-адресом определенной организации.bindПРИМЕЧАНИЕВ качестве альтернативы можно запустить одиночный сервер, связанный с универсальным адресом. Когда происходит соединение, сервер вызывает функцию getsockname, чтобы получить от клиента IP-адрес получателя, который (см. наше обсуждение ранее) может быть равен 198.69.10.128,198.69.10.129 и т.д. Затем сервер обрабатывает запрос клиента па основе именно того IP-адреса, к которому было направлено это соединение.
Одним из преимуществ связывания с конкретным IP-адресом является то, что демультиплексирование данного IP-адреса с процессом сервера выполняется ядром.
Следует внимательно относиться к различию интерфейса, на который приходит пакет, и IP-адреса получателя этого пакета. В разделе 8.8 мы поговорим о моделях систем с гибкой привязкой (weak end system) и с жесткой привязкой (strong end system). Большинство реализаций используют первую модель, то есть считают обычным явлением принятие пакета на интерфейсе, отличном от указанного в IP-адресе получателя. (При этом подразумевается узел с несколькими сетевыми интерфейсами.) При связывании с сокетом конкретного IP-адреса на этом сокете будут приниматься дейтаграммы с заданным IP-адресом получателя, и только они. Никаких ограничений на принимающий интерфейс не накладывается — эти ограничения возникают только в случае, если используется модель системы с жесткой привязкой.
Общей ошибкой выполнения функции
являетсяbind, указывающая на то, что адрес уже используется. Более подробно мы поговорим об этом в разделе 7.5, когда будем рассматривать параметры сокетовEADDRINUSEиSO_REUSEADDR.SO_REUSEPORT4.5. Функция listen
Функция
вызывается только сервером TCP и выполняет два действия.listen1. Когда сокет создается с помощью функции
, считается, что это активный сокет, то есть клиентский сокет, который запустит функциюsocket. Функцияconnectпреобразует неприсоединенный сокет в пассивный сокет, запросы на подключение к которому начинают приниматься ядром. В терминах диаграммы перехода между состояниями TCP (см. рис. 2.4) вызов функцииlistenпереводит сокет из состояния CLOSED в состояние LISTEN.listen