355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Уильям Ричард Стивенс » UNIX: разработка сетевых приложений » Текст книги (страница 30)
UNIX: разработка сетевых приложений
  • Текст добавлен: 17 сентября 2016, 20:42

Текст книги "UNIX: разработка сетевых приложений"


Автор книги: Уильям Ричард Стивенс


Соавторы: Эндрю М. Рудофф,Билл Феннер

Жанр:

   

ОС и Сети


сообщить о нарушении

Текущая страница: 30 (всего у книги 88 страниц) [доступный отрывок для чтения: 32 страниц]

Запуск программы

Мы запустили клиент и сервер на разных компьютерах с FreeBSD, между которыми был установлен настраиваемый маршрутизатор (рис. 10.4). Маршрутизатор может создавать задержку и сбрасывать часть пакетов. Сначала мы запускаем программу без сброса пакетов на маршрутизаторе.

Рис. 10.4. Тестовая конфигурация сети

Мы запускаем сервер с аргументом 0 в командной строке, благодаря чему сервер не увеличивает номер потока при отправке эхо-ответа.

Затем мы запускаем клиент, передавая ему в командной строке адрес эхо-сервера и дополнительный аргумент, указывающий на необходимость отправки сообщений по всем потокам одновременно.

freebsd4% sctpclient01 10.1.4.1 echo

Echoing messages to all streams

Hello

From str:0 seq:0 (assoc:0xc99e15a0):Hello.msg.0

From str:1 seq:0 (assoc.0xc99e15a0):Hello.msg.1

From str:2 seq:0 (assoc:0xc99e15a0):Hello.msg.2

From str:3 seq:0 (assoc 0xc99e15a0):Hello.msg.3

From str:4 seq:0 (assoc.0xc99e15a0):Hello.msg.4

From str:5 seq:0 (assoc:0xc99e15a0):Hello.msg.5

From str:6 seq:0 (assoc.0xc99e15a0):Hello.msg.6

From str:7 seq:0 (assoc:0xc99e15a0):Hello.msg.7

From str:8 seq:0 (assoc:0xc99e15a0):Hello.msg.8

From str:9 seq:0 (assoc:0xc99e15a0).Hello.msg.9

^D

freebsd4%

В отсутствие потерь при передаче клиент получает ответы сервера в том же порядке, в котором отправляет запросы. Изменим параметры маршрутизатора таким образом, чтобы терять 10% всех пакетов, передаваемых в обоих направлениях, и перезапустим клиент.

freebsd4% sctpclient01 10.1.4.1 echo

Echoing messages to all streams

Hello

From str:0 seq:0 (assoc:0xc99e15a0):Hello.msg.0

From str:2 seq:0 (assoc:0xc99e15a0):Hello.msg.2

From str:3 seq:0 (assoc:0xc99e15a0):Hello.msg.3

From str:5 seq:0 (assoc:0xc99e15a0):Hello.msg.5

From str:1 seq:0 (assoc:0xc99e15a0):Hello.msg.1

From str:8 seq:0 (assoc:0xc99e15a0):Hello.msg.8

From str:4 seq:0 (assoc:0xc99e15a0):Hello.msg.4

From str:7 seq:0 (assoc:0xc99e15a0):Hello.msg.7

From str:9 seq:0 (assoc:0xc99e15a0):Hello.msg.9

From str:6 seq:0 (assoc:0xc99e15a0):Hello msg.6

^D

freebsd4%

Можно проверить, действительно ли сообщения в каждом из потоков доставляются в правильном порядке, если изменить клиента так, чтобы он отправлял по два сообщения в поток. Кроме того, мы добавим к сообщению суффикс с его номером, чтобы отличать эхо-ответы друг от друга. Измененная функция клиента представлена в листинге 10.5.

Листинг 10.5. Изменения в функции sctp_strcliecho

//sctp/sctp_strcliecho2.c

21 for (i =0; i < SERV_MAX_SCTP_STRM; i++) {

22  snprintf(sendline + strsz, sizeof(sendline) – strsz,

23   ".msg.%d 1", i);

24  Sctp_sendmsg(sock_fd, sendline, sizeof(sendline),

25   to, tolen, 0, 0, i, 0, 0);

26  snprintf(sendline + strsz, sizeof(sendline) – strsz,

27   ".msg.%d 2", i);

28  Sctp_sendmsg(sock_fd, sendline, sizeof(sendline),

29   to, tolen, 0, 0, i, 0, 0);

30 }

31 for (i = 0; i < SERV_MAX_SCTP_STRM*2, i++) {

32  len = sizeof(peeraddr);

Первое сообщение: добавление номера и отправка

22-25 Клиент добавляет к первому сообщению его номер, с помощью которого мы сможем отслеживать отправленные сообщения. Затем сообщение отсылается вызовом sctp_sendmsg.

Второе сообщение: добавление номера и отправка

26-29 Номер сообщения изменяется с единицы на двойку, после чего сообщение отсылается по тому же потоку.

Считывание и отображение эхо-ответа

31 Здесь требуется лишь одно незначительное изменение: количество ожидаемых ответов эхо-сервера должно быть удвоено.

Запуск измененной программы

Запустив сервер и измененный клиент, мы получаем следующий результат:

freebsd4% sctpclient01 10.1.4.1 echo

Echoing messages to all streams

Hello

From str:0 seq:0 (assoc:0xc99e15a0):Hello.msg.0 1

From str:0 seq:1 (assoc:0xc99e15a0):Hello.msg.0 2

From str:1 seq:0 (assoc:0xc99e15a0):Hello.msg.1 1

From str:4 seq:0 (assoc:0xc99e15a0):Hello.msg.4 1

From str:5 seq:0 (assoc:0xc99e15a0):Hello.msg.5 1

From str:7 seq:0 (assoc:0xc99e15a0):Hello.msg.7 1

From str:8 seq:0 (assoc:0xc99e15a0):Hello.msg.8 1

From str:9 seq:0 (assoc:0xc99e15a0):Hello.msg.9 1

From str:3 seq:0 (assoc:0xc99e15a0):Hello.msg.3 1

From str:3 seq:0 (assoc:0xc99e15a0):Hello.msg.3 2

From str:1 seq:0 (assoc:0xc99e15a0):Hello.msg.1 2

From str:5 seq:0 (assoc:0xc99e15a0):Hello.msg.5 2

From str:2 seq:0 (assoc:0xc99e15a0):Hello.msg.2 1

From str:6 seq:0 (assoc:0xc99e15a0):Hello.msg.6 1

From str:6 seq:0 (assoc:0xc99e15a0):Hello.msg.6 2

From str:2 seq:0 (assoc:0xc99e15a0):Hello.msg.2 2

From str:7 seq:0 (assoc:0xc99e15a0):Hello.msg.7 2

From str:8 seq:0 (assoc:0xc99e15a0):Hello.msg.8 2

From str:9 seq:0 (assoc:0xc99e15a0):Hello.msg.9 2

From str:4 seq:0 (assoc:0xc99e15a0):Hello.msg.4 2

^D

freebsd4%

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

10.6. Управление количеством потоков

Мы рассмотрели пример использования потоков SCTP, но пока что мы не знаем, каким образом можно контролировать количество потоков, запрашиваемых конечной точкой в процессе инициализации ассоциации. В предыдущих примерах мы работали с тем количеством исходящих потоков, которое было установлено в системе по умолчанию. В реализации SCTP для FreeBSD, созданной в рамках проекта KAME, это значение равно 10. А что, если серверу и клиенту нужно больше десяти потоков? В листинге 10.6 мы приводим модификацию кода сервера, позволяющую увеличивать количество потоков, запрашиваемое при создании ассоциации. Обратите внимание, что данный параметр сокета должен быть изменен до создания ассоциации.

Листинг 10.6. Вариант сервера, допускающий увеличение числа потоков

//sctp/sctpserv02.c

14 if (argc 2)

15  stream_increment = atoi(argv[1]);

16 sock_fd = Socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);

17 bzero(&initm, sizeof(initm));

18 initm.sinit_num_ostreams = SERV_MORE_STRMS_SCTP;

19 Setsockopt(sock_fd, IPPROTO_SCTP, SCTP_INITMSG, &initm, sizeof(initm));

Предварительная настройка

14-16 Как и в предыдущей версии программы, сервер устанавливает флаг stream_incrementв соответствии с дополнительным параметром командной строки, после чего открывает сокет.

Изменение запрашиваемого количества потоков

17-19 Все сделанные модификации относятся именно к этим строкам. Сначала сервер обнуляет структуру sctp_initmsg. Это изменение гарантирует, что вызов setsockoptне приведет к непреднамеренному изменению каких-либо иных значений кроме того, которое нас интересует. Затем сервер устанавливает поле sinit_max_ostreamsравным количеству запрашиваемых потоков. После этого вызывается функция setsockoptс параметром сокета SCTP_INITMSGдля установки параметров сообщения INIT.

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

10.7. Управление завершением соединения

В наших примерах на клиента была возложена ответственность по завершению ассоциации, для чего ему приходилось закрывать сокет. Но закрытие сокета не всегда является желаемой операцией с точки зрения приложения. Кроме того, серверу не нужно оставлять ассоциацию открытой после отправки эхо-ответа. В описанных ситуациях применяются альтернативные механизмы завершения ассоциации. Для сокетов типа «один-ко-многим» доступно два метода: корректное и аварийное закрытие.

Если сервер хочет закрыть ассоциацию после отправки сообщения, он должен добавить флаг MSG_EOF в это сообщение, поместив его в поле sinfo_flagsструктуры sctp_sndrcvinfo. Этот флаг закрывает ассоциацию после подтверждения приема отсылаемого сообщения. Альтернативный метод состоит в установке флага MSG_ABORTв том же поле sinfo_flags. При этом происходит немедленное закрытие ассоциации с отправкой порции ABORT (аналог TCP-сегмента RST). Данные, находящиеся в буфере отправки, сбрасываются. Однако закрытие сеанса SCTP порцией ABORT не приводит к негативным последствиям типа пропущенного состояния TIME_WAIT, как это происходит в TCP. В листинге 10.7 показана новая версия эхо-сервера, инициирующая корректное завершение соединения одновременно с отправкой эхо-ответа клиенту. В листинге 10.8 показана версия клиента, отправляющая порцию ABORT перед закрытием сокета.

Листинг 10.7. Сервер, закрывающий ассоциацию после отправки ответа

//sctp/sctpserv03.c

25 for (;;) {

26  len = sizeof(struct sockaddr_in);

27  rd_sz = Sctp_recvmsg(sock_fd, readbuf, sizeof(readbuf),

28   (SA*)&cliaddr, &len, &sri, &msg_flags);

29  if (stream_increment) {

30   sri.sinfo_stream++;

31   if (sri.sinfo_stream >=

32    sctp_get_no_strms(sock_fd, (SA*)&cliaddr, len))

33    sri.sinfo_stream = 0;

34  }

35  Sctp_sendmsg(sock_fd, readbuf, rd_sz,

36   (SA*)&cliaddr, len,

37   sri.sinfo_ppid,

38   (sri.sinfo_flags | MSG_EOF), sri.sinfo_stream, 0, 0);

39 }

Отправка ответа с закрытием ассоциации

38 Изменение кода сервера состоит в том, что мы добавляем флаг MSG_EOFк прочим флагам в вызове sctp_sendmsgоперацией логического ИЛИ. Благодаря этому сервер закрывает ассоциацию после подтверждения доставки сообщения.

Листинг 10.8. Клиент, выполняющий аварийное закрытие ассоциации

//sctp/sctpclient02.c

25 if (echo_to_all == 0)

26  sctpstr_cli(stdin, sock_fd, (SA*)&servaddr, sizeof(servaddr));

27 else

28  sctpstr_cli_echoall(stdin, sock_fd, (SA*)&servaddr,

29   sizeof(servaddr));

30 strcpy(byemsg, "goodbye");

31 Sctp_sendmsg(sock_fd, byemsg, strlen(byemsg),

32  (SA*)&servaddr, sizeof(servaddr), 0, MSG_ABORT, 0, 0, 0);

33 Close(sock_fd);

Аварийное закрытие ассоциации

30-32 Клиент подготавливает сообщение об аварийном закрытии ассоциации, вызванном пользовательской ошибкой. Затем функция sctp_sendmsgвызывается с флагом MSG_ABORT. При этом отправляется порция данных ABORT, что приводит к немедленному закрытию ассоциации. В порцию данных включается код пользовательской ошибки и сообщение («goodbye») в поле причины ошибки вышележащего уровня.

Закрытие дескриптора сокета

33 Хотя ассоциация и была завершена, дескриптор сокета все равно закрыть нужно, чтобы освободить связанные с ним системные ресурсы.

10.8. Резюме

Мы изучили простой пример клиента и сервера SCTP общим объемом около 150 строк кода. Обе программы работали с сокетами SCTP типа «один-ко-многим». Сервер был написан в последовательном стиле, который часто используется при работе с такими сокетами. Он считывал сообщения и отвечал на них по тому же потоку, из которого они приходили, или по потоку с увеличенным на единицу номером. Затем мы исследовали проблему блокирования очереди, изменив программу клиента таким образом, чтобы подчеркнуть особенности ситуации и продемонстрировать использование потоков SCTP для решения проблемы. После этого мы показали, каким образом можно изменить количество потоков при помощи одного из множества параметров сокета, используемых для управления поведением SCTP. Наконец, мы снова изменили код сервера и клиента, чтобы показать корректное и аварийное закрытие ассоциации.

Углубленное исследование SCTP будет проведено в главе 23.

Упражнения

1. Что произойдет с программой в листинге 10.1, если SCTP вернет сообщение об ошибке? Каким образом вы можете устранить указанный недостаток программы?

2. Что произойдет, если сервер завершит работу, не ответив на сообщения? Может ли клиент каким-либо образом получить уведомление об этом событии?

3. В листинге 10.7 в строке 22 аргумент out_szустанавливается равным 800 байт. Как вы думаете, почему мы выбрали именно это значение? Существует ли лучший способ найти оптимальное значение этого аргумента?

4. Как повлияет алгоритм Нагла (см. раздел 7.10) на нашего клиента из листинга 10.7? Не лучше ли будет отключить алгоритм Нагла для этой программы? Воплотите это изменение в код клиента и сервера.

5. В разделе 10.6 мы утверждали, что приложению следует изменять количество потоков до установки ассоциации. Что произойдет в противном случае?

6. Когда мы говорили о количестве потоков, мы подчеркнули, что только для сокетов типа «один-ко-многим» можно увеличить количество потоков при помощи вспомогательных данных. Почему это так? (Подсказка: вспомогательные данные необходимо передавать с сообщениями.)

7. Почему сервер может не отслеживать открытые ассоциации? Опасно ли это?

8. В разделе 10.7 мы изменили сервер так, что он стал закрывать ассоциацию после отправки каждого сообщения. Вызовет ли это какие-либо проблемы? Хорошее ли это решение с точки зрения архитектуры приложения?

Глава 11
Преобразования имен и адресов
11.1. Введение

Во всех предшествующих примерах мы использовали численные адреса узлов (например, 206.6.226.33) и численные номера портов для идентификации серверов (например, порт 13 для стандартного сервера времени и даты и порт 9877 для нашего эхо-сервера). Однако по ряду соображений предпочтительнее использовать имена вместо чисел: во-первых, имена проще запоминаются, во-вторых, если численный адрес поменяется, имя можно сохранить, и в-третьих, с переходом на IPv6 численные адреса становятся значительно длиннее, что увеличивает вероятность ошибки при вводе адреса вручную. В этой главе описываются функции, выполняющие преобразование имен и адресов: gethostbynameи gethostbyaddrдля преобразования имен узлов и IP-адресов, и getservbynameи getservbyportдля преобразования имен служб и номеров портов. Здесь же мы рассмотрим две независимые от протоколов функции getaddrinfoи getnameinfo, осуществляющие преобразование между IP-адресами и именами узлов, а также между именами служб и номерами портов.

11.2. Система доменных имен

Система доменных имен( Domain Name System, DNS) используется прежде всего для сопоставления имен узлов и IP-адресов. Имя узла может быть либо простым(simple name), таким как solarisили bsdi, либо полным доменным именем(f ully qualified domain name, FQDN), например solaris.unpbook.com..

ПРИМЕЧАНИЕ

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

В этом разделе мы рассмотрим только основы DNS, необходимые нам для сетевого программирования. Читатели, интересующиеся более подробным изложением вопроса, могут обратиться к главе 14 [111] и к [1]. Дополнения, требуемые для IPv6, изложены в RFC 1886 [121].

Записи ресурсов

Записи в DNS называются записями ресурсов( resource records, RR). Нас интересуют только несколько типов RR.

■ А. Запись типа А преобразует имя узла в 32-разрядный адрес IPv4. Вот, например, четыре записи DNS для узла freebsdв домене unpbook.com, первая из которых – это запись типа А:

freebsd IN А    12.106.32.254

        IN AAAA 3ffe:b80:1f8d:1:a00:20ff:fea7:686b

        IN MX   5 freebsd.unpbook.com.

        IN MX   10 mailhost.unpbook.com.

■ AAAA. Запись типа AAAA, называемая «четыре А» (quad А), преобразует имя узла в 128-разрядный адрес IPv6. Название «четыре А» объясняется тем, что 128-разрядный адрес в четыре раза больше 32-разрядного адреса.

■ PTR. Запись PTR (pointer records – запись указателя) преобразует IP-адрес в имя узла. Четыре байта адреса IPv4 располагаются в обратном порядке. Каждый байт преобразуется в десятичное значение ASCII (0-255), а затем добавляется in-addr.arpa. Получившаяся строка используется в запросе PTR.

32 полубайта 128-разрядного адреса IPv6 также располагаются в обратном порядке. Каждый полубайт преобразуется в соответствующее шестнадцатеричное значение ASCII ( 0-9, a-f)и добавляется к ip6.arpa.

Например, две записи PTR для нашего узла freebsdбудут выглядеть так:

254.32.106.12 in-addr.arpa

b.6.8.6.7.a.e.f.f.f.0.2.0.0.a.0.1.0.0.0.d.8.f.1.0.8.b.0.e.f.f.3.ip6.arpa

■ MX. Запись типа MX (Mail Exchange Record) определяет, что узел выступает в роли «маршрутизирующего почтового сервера» для заданного узла. В приведенном выше примере для узла solarisпредоставлено две записи типа MX. Первая имеет предпочтительное значение 5, вторая – 10. Когда существует множество записей типа MX, они используются в порядке предпочтения начиная с наименьшего значения.

ПРИМЕЧАНИЕ

Мы не используем в примерах книги записей типа MX, но упоминаем о них, потому что они широко используются в реальной жизни.

■ CNAME. Аббревиатура CNAME означает «каноническое имя» (canonical name). Обычно такие записи используются для присвоения имен распространенным службам, таким как ftpи www. При использовании имен служб вместо действительного имени узла перемещение службы на другой узел становится прозрачным (то есть незаметным для пользователя). Например, для нашего узла linuxканоническими именами могут быть следующие записи:

ftp IN CNAME linux.unpbook.com.

www IN CNAME linux.unpbook.com.

Сейчас прошло еще слишком мало времени с момента появления протокола IPv6, чтобы сказать, каких соглашений будут придерживаться администраторы для узлов, поддерживающих и IPv4, и IPv6. В нашем примере мы задали узлу freebsdи запись типа А, и запись типа AAAA. Автор помещает и запись типа А, и запись типа AAAA под каноническим именем узла (как показано ниже) и создает три записи RR. Первая запись RR, имя которой оканчивается на -4, содержит запись типа А; вторая, с именем, оканчивающимся на -6, содержит запись типа AAAA; а третья запись RR, имя которой оканчивается на -611, содержит запись типа AAAA с локальным в пределах физической подсети (link-local, см. главу 19) адресом узла (что иногда удобно использовать в целях отладки). Все записи для другого нашего узла будут выглядеть так:

aix-4   IN А    206.62.226.43

aix     IN А    206.62.226.43

        IN MX   5 aix.unpbook.com.

        IN MX   10 mailhost.unpbook.com.

Aix-4   IN A    192.168.42.2

aix-6   IN AAAA 3ffe:b80:1f8d:2:204:acff:fe17:bf38

aix-611 IN AAAA fe80::204:acff:fe17:bf38

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

Распознаватели и серверы имен

Организации обычно работают с одним или несколькими серверами имен (name servers). Часто в качестве сервера используется программа BIND (Berkeley Internet Name Domain). Приложения, такие как клиенты и серверы, которые мы создаем в этой книге, соединяются с сервером DNS при помощи вызова функций из библиотеки, называемой распознавателем( resolver). Обычные функции распознавателя – gethostbynameи gethostbyaddr, и обе они описаны в этой главе. Первая находит адрес узла по его имени, а вторая – наоборот.

На рис. 11.1 показано типичное расположение приложений, распознавателей и серверов имен. В некоторых системах код распознавателя содержится в системной библиотеке и встраивается в приложение, когда оно создается. В других системах имеется централизованный демон-распознаватель, к которому обращаются все приложения. Системная библиотека выполняет удаленные вызовы процедур такого распознавателя. В любом случае код приложения вызывает код распознавателя посредством обычных вызовов, чаще всего gethostbynameи gethostbyaddr.

Рис. 11.1. Типичное расположение приложений, распознавателей и серверов имен

Код распознавателя считывает из файлов конфигурации, зависящих от системы, расположение серверов имен организации. (Мы говорим «серверы имен», употребляя множественное число, потому что большинство организаций работают с несколькими серверами имен, хотя мы и показываем на рисунке только один локальный сервер.) Файл /etc/resolv.confобычно содержит IP-адреса локальных серверов имен.

ПРИМЕЧАНИЕ

Было бы удобно указывать в файле /etc/resolv.conf имена, а не IP-адреса серверов имен, потому что имена удобнее запоминать и редактировать, однако это возвратило бы нас к вечной проблеме курицы и яйца: каким образом распознать имя сервера имен?

Распознаватель посылает запрос локальному серверу имен, используя UDP. Если локальный сервер имен не знает ответа, он обычно запрашивает другие серверы имен через Интернет, также используя UDP. Если ответ слишком велик, чтобы поместиться в один UDP-пакет, распознаватель автоматически переключается на TCP.


    Ваша оценка произведения:

Популярные книги за неделю