UNIX: разработка сетевых приложений
ПРИМЕЧАНИЕМногие более ранние системы, такие как 4.2BSD, некорректно прерывали попытки установления соединения при получении сообщения ICMP о недоступности получателя. Это было неверно, поскольку данная ошибка ICMP может указывать на временную неисправность. Например, может быть так, что эта ошибка вызвана проблемой маршрутизации, которая исправляется в течение 15 с.
Обратите внимание, что мы не включили ENETUNREACH в табл. А.5 несмотря на то, что сеть получателя действительно может быть недоступна. Недоступность сети считается устаревшей ошибкой, и даже если 4.4BSD получает такое сообщение, приложению возвращается EHOSTUNREACH.
Эти ошибки мы можем наблюдать на примере нашего простого клиента, созданного в листинге 1.1. Сначала мы указываем адрес нашего собственного узла (127.0.0.1), на котором работает сервер времени и даты, и видим обычный вывод:
solaris % <b>daytimetcpcli 127.0.0.1</b>Sun Jul 27 22:01:51 2003Укажем IP-адрес другого компьютера (HP-UX):
solaris % <b>daytimecpcli 192.6.38.100</b>Sun Jul 27 22:04:59 PDT 2003Затем мы задаем IP-адрес в локальной подсети (192.168.1/24) с несуществующим адресом узла (100). Когда клиент посылает запросы ARP (запрашивая аппаратный адрес узла), он не получает никакого ответа:
solaris % <b>daytimetcpcli 192.168.1.100</b>connect error: Connection timed outМы получаем сообщение об ошибке только по истечении времени выполнения функции
(которое, как мы говорили, для Solaris 9 составляет 3 мин). Обратите внимание, что наша функцияconnectвыдает текстовое сообщение, соответствующее коду ошибкиerr_sys.ETIMEDOUTВ следующем примере мы пытаемся обратиться к локальному маршрутизатору, на котором не запущен сервер времени и даты:
solaris % <b>daytimetcpcli 192.168.1.5</b>connect error: Connection refusedСервер отвечает немедленно, отправляя сегмент RST.
В последнем примере мы пытаемся обратиться к недоступному адресу из сети Интернет. Просмотрев пакеты с помощью программы
, мы увидим, что маршрутизатор, находящийся на расстоянии шести прыжков от нас, возвращает сообщение ICMP о недоступности узла:tcpdumpsolaris % <b>daytimetcpcli 192.3.4.5</b>connect error: No route to hostКак и в случае ошибки
, в этом примере функцияETIMEDOUTвозвращает ошибкуconnectтолько после ожидания в течение определенного времени.EHOSTUNREACHВ терминах диаграммы перехода состояний TCP (см. рис. 2.4) функция
переходит из состоянияconnect(состояния, в котором сокет начинает работать при создании с помощью функцииCLOSED) в состояниеsocket, а затем, при успешном выполнении, в состояниеSYN_SENT. Если выполнение функцииESTABLISHEDокажется неудачным, сокет больше не используется и должен быть закрыт. Мы не можем снова вызвать функциюconnectдля сокета. В листинге 11.4 вы увидите, что если функцияconnectвыполняется в цикле, проверяя каждый IP-адрес данного узла, пока он не заработает, то каждый раз, когда выполнение функции оказывается неудачным, мы должны закрыть дескриптор сокета с помощью функцииconnectи снова вызвать функциюclose.socket4.4. Функция bind
Функция
связывает сокет с локальным адресом протокола. В случае протоколов Интернета адрес протокола — это комбинация 32-разрядного адреса IPv4 или 128-разрядного адреса IPv6 с 16-разрядным номером порта TCP или UDP.bind#include <sys/socket.h>int bind(int <i>sockfd</i>, const struct sockaddr *<i>myaddr</i>, socklen_t <i>addrlen</i>);<i>Возвращает: 0 в случае успешного выполнения, -1 в случае ошибки</i>ПРИМЕЧАНИЕВ руководстве при описании функции bind говорилось: «функция bind присваивает имя неименованному сокету». Использование термина «имя» спорно, обычно оно вызывает ассоциацию с доменными именами (см. главу 11), такими как foo.bar.com. Функция bind не имеет ничего общего с именами. Она задает сокету адрес протокола, а что означает этот адрес — зависит от самого протокола.
Вторым аргументом является указатель на специфичный для протокола адрес, а третий аргумент — это размер структуры адреса. В случае TCP вызов функции
позволяет нам задать номер порта или IP-адрес, а также задать оба эти параметра или вообще не указывать ничего.bind■ Серверы связываются со своим заранее известным портом при запуске. Мы видели это в листинге 1.5. Если клиент или сервер TCP не делает этого, ядро выбирает динамически назначаемый порт для сокета либо при вызове функции
, либо при вызове функцииconnect. Клиент TCP обычно позволяет ядру выбирать динамически назначаемый порт, если приложение не требует зарезервированного порта (см. рис. 2.10), но сервер TCP достаточно редко предоставляет ядру право выбора, так как обращение к серверам производится через заранее известные порты.listenПРИМЕЧАНИЕИсключением из этого правила являются серверы удаленного вызова процедур RPC (Remote Procedure Call). Обычно они позволяют ядру выбирать динамически назначаемый порт для их прослушиваемого сокета, поскольку затем этот порт регистрируется программой отображения портов RPC. Клиенты должны соединиться с этой программой, чтобы получить номер динамически назначаемого порта до того, как они смогут соединиться с сервером с помощью функции connect. Это также относится к серверам RPC, использующим протокол UDP.
■ С помощью функции
процесс может связать конкретный IP-адрес с сокетом. IP-адрес должен соответствовать одному из интерфейсов узла. Так определяется IP-адрес, который будет использоваться для отправляемых через сокет IP-дейтаграмм. При этом для сервера TCP на сокет накладывается ограничение: он может принимать только такие входящие соединения клиента, которые предназначены именно для этого IP-адреса.bindОбычно клиент TCP не связывает IP-адрес с сокетом при помощи функции
. Ядро выбирает IP-адрес отправителя в момент подключения клиента к сокету, основываясь на используемом исходящем интерфейсе, который, в свою очередь, зависит от маршрута, требуемого для обращения к серверу [128, с. 737].bindЕсли сервер TCP не связывает IP-адрес с сокетом, ядро назначает ему IP-адрес (указываемый в исходящих пакетах), который совпадает с адресом получателя сегмента SYN клиента [128, с. 943].