UNIX: разработка сетевых приложений - Уильям Стивенс
Шрифт:
Интервал:
Закладка:
solaris26 % tpi_daytime 192.3.4.5
tpi_connect2: T_DISCON_IND from conn (148)
На этот раз мы получаем ошибку EHOSTUNREACH. Различие в том, что в первый раз не было возвращено сообщение ICMP о недоступности узла, а во второй раз мы получили это сообщение.
Следующая функция, которую мы рассмотрим, — это tpi_read, показанная в листинге 31.5. Она считывает данные из потока.
Листинг 31.5. Функция tpi_read: считывание данных из потока
//streams/tpi_read.c
1 #include "tpi_daytime.h"
2 ssize_t
3 tpi_read(int fd, void *buf, size_t len)
4 {
5 struct strbuf ctlbuf;
6 struct strbuf datbuf;
7 union T_primitives rcvbuf;
8 int flags;
9 ctlbuf maxlen = sizeof(union T_primitives);
10 ctlbuf.buf = (char*)&rcvbuf;
11 datbuf.maxlen = len;
12 datbuf.buf = buf;
13 datbuf.len = 0;
14 flags = 0;
15 Getmsg(fd, &ctlbuf, &datbuf, &flags);
16 if (ctlbuf.len >= (int)sizeof(long)) {
17 if (rcvbuf.type == T_DATA_IND)
18 return (datbuf.len);
19 else if (rcvbuf.type == T_ORDREL_IND)
20 return (0);
21 else
22 err_quit("tpi_read: unexpected type %d", rcvbuf.type);
23 } else if (ctlbuf.len == -1)
24 return (datbuf.len);
25 else
26 err_quit("tpi_read: bad length from getmsg");
27 }
Считывание управляющей информации и данных, обработка ответа9-26 На этот раз мы вызываем функцию getmsg для считывания как данных, так и управляющей информации. Структура strbuf, предназначенная для данных, указывает на буфер вызывающего процесса. В потоке события могут развиваться по четырем различным сценариям.
■ Данные могут прибыть в виде сообщения M_DATA, и указанием на это является возвращенное значение длины управляющей информации, равное -1. Данные скопированы в буфер вызывающего процесса функцией getmsg, и функция просто возвращает длину этих данных.
■ Данные могут прибыть как сообщение T_DATA_IND, в этом случае управляющая информация будет содержаться в структуре T_data_ind:
struct T_data_ind {
long PRIM_type; /* T_DATA_IND */
long MORE_flag; /* еще данные */
};
Если возвращено такое сообщение, мы игнорируем поле MORE_flag (оно вообще не задается для таких протоколов, как TCP) и просто возвращаем длину данных, скопированных в буфер вызывающего процесса функцией getmsg.
■ Сообщение T_ORDREL_IND возвращается, если все данные получены и следующим элементом является сегмент FIN:
struct T_ordrel_ind {
long PRIM_type; /* T_ORDREL_IND */
};
Это нормальное завершение. Мы просто возвращаем нулевое значение, указывая вызывающему процессу, что по соединению получен признак конца файла.
■ Сообщение T_DISCON_IND возвращается, если произошел разрыв соединения. Наша последняя функция — это tpi_close, показанная в листинге 31.6.
Листинг 31.6. Функция tpi_close: отправка запроса о завершении собеседнику
//streams/tpi_close.c
1 #include "tpi_daytime.h"
2 void
3 tpi_close(int fd)
4 {
5 struct T_ordrel_req ordrel_req;
6 struct strbuf ctlbuf;
7 ordrel_req PRIM_type = T_ORDREL_REQ;
8 ctlbuf.len = sizeof(struct T_ordrel_req);
9 ctlbuf.buf = (char*)&ordrel_req;
10 Putmsg(fd, &ctlbuf, NULL, 0);
11 Close(fd);
12 }
Отправка запроса о завершении собеседнику7-10 Мы формируем структуру T_ordrel_req:
struct T_ordrel_req {
long PRIM_type; /* T_ORDREL_REQ */
};
и посылаем ее как сообщение M_PROTO с помощью функции putmsg. Это соответствует функции XTI t_sndrel.
Этот пример позволил нам почувствовать специфику TPI. Приложение посылает сообщения вниз по потоку (запросы), а поставщик посылает сообщения вверх по потоку (ответы). Некоторые обмены сообщений организованы согласно простому сценарию «запрос-ответ» (связывание локального адреса), в то время как остальные могут занять некоторое время (установление соединения), позволяя нам заняться чем-то другим в процессе ожидания ответа. Для знакомства с TPI мы выбрали этот пример (написание клиента TCP) из-за его относительной простоты. Если бы мы решили написать с использованием TPI TCP-сервер, обрабатывающий одновременно несколько соединений, это было бы гораздо сложнее.
ПРИМЕЧАНИЕМожно сравнить количество системных вызовов, необходимых для осуществления определенных сетевых операций, показанных в этой главе, в случае применения TPI и когда используется ядро, реализующее сокеты. Связывание с локальным адресом в случае TPI требует двух системных вызовов, но в случае сокетного ядра требуется только один вызов [128, с. 454]. Для установления соединения на блокируемом дескрипторе с использованием TPI требуется три системных вызова, а в случае сокетного ядра — только один [128, с. 466].
31.7. Резюме
Иногда сокеты реализуются с использованием потоков STREAMS. Для обеспечения доступа к потоковой подсистеме вводятся четыре новые функции: getmsg, putmsg, getpmsg и putpmsg. Также в потоковой подсистеме широко используется уже описанная ранее функция ioctl.
TPI представляет собой потоковый интерфейс системы SVR4, предоставляющий доступ из верхних уровней на транспортный уровень. Он используется как сокетами, так и XTI, как показано на рис. 31.3. В этой главе в качестве примера использования основанного на сообщениях интерфейса мы разработали версию клиента времени и даты, в котором непосредственно применяется интерфейс TPI.
Упражнения
1. В листинге 31.6 мы вызываем функцию putmsg, чтобы отправить вниз по потоку запрос на нормальное завершение соединения, а затем немедленно вызываем функцию close для закрытия потока. Что произойдет, если наш запрос будет потерян потоковой подсистемой, а мы закроем поток?
Приложения
Приложение А
Протоколы IPv4, IPv6, ICMPv4 и ICMFV6
А.1. Введение
В этом приложении приведен обзор протоколов IPv4, IPv6, ICMPv4 и ICMPv6. Данный материал позволяет глубже понять рассмотренные в главе 2 протоколы TCP и UDP. Некоторые возможности IP и ICMP рассматриваются также более подробно и в других главах, например параметры IP (см. главу 27), и программы ping и traceroute (см. главу 28).
А.2. Заголовок IPv4
Уровень IP обеспечивает не ориентированную на установление соединения (connectionless) и ненадежную службу доставки дейтаграмм (RFC 791 [94]). Уровень IP делает все возможное для доставки IP-дейтаграммы определенному адресату, но не гарантирует, что дейтаграмма будет доставлена, прибудет в нужном порядке относительно других пакетов, а также будет доставлена в единственном экземпляре. Если требуется надежная доставка дейтаграммы, она должна быть обеспечена на более высоком уровне. В случае приложений TCP и SCTP надежность обеспечивается транспортным уровнем. Приложению UDP надежность должно обеспечивать само приложение, поскольку уровень UDP также не предоставляет гарантии надежной доставки дейтаграмм, что было показано на примере в разделе 22.5.
Одной из наиболее важных функций уровня IP является маршрутизация (routing). Каждая IP-дейтаграмма содержит адрес отправителя и адрес получателя. На рис. А.1 показан формат заголовка Ipv4.
Рис. А.1. Формат заголовка IPv4
■ Значение 4-разрядного поля версия (version) равно 4. Это версия протокола IP, используемая с начала 80-х.
■ В поле длина заголовка (header length) указывается полная длина IP-заголовка, включающая любые параметры, описанные 32-разрядными словами. Максимальное значение этого 4-разрядного поля равно 15, и это значение задает максимальную длину IP-заголовка 60 байт. Таким образом, если заголовок занимает фиксированные 20 байт, то 40 байт остается на различные параметры.
■ 16-разрядное поле кода дифференцированных сервисов (Differentiated Services Code Point, DSCP) (RFC 2474 [82]) и 2-разрядное поле явного уведомления о загруженности сети (Explicit Congestion Notification, ECN) (RFC 3168 [100]) заменили 8-разрядное поле тип службы (сервиса) (type-of-service, TOS), которое описывалось в RFC 1349 [5]. Все 8 разрядов этого поля можно установить с помощью параметра сокета IP_TOS (см. раздел 7.6), хотя ядро может перезаписать любое установленное нами значение при проведении политики Diffserv или реализации ECN.
■ Поле общая длина (total length) имеет размер 16 бит и задает полную длину IP- дейтаграммы в байтах, включая заголовок IPv4. Количество данных в дейтаграмме равно значению этого поля минус длина заголовка, умноженная на 4. Данное поле необходимо, поскольку некоторые каналы передачи данных заполняют кадр до некоторой минимальной длины (например, Ethernet) и возможна ситуация, когда размер действительной IP-дейтаграммы окажется меньше требуемого минимума.