Уильям Стивенс - UNIX: разработка сетевых приложений
Мы можем обойти как XTI, так и сокеты, и использовать непосредственно TPI. В этом разделе мы заново перепишем код нашего простого клиента времени и даты с использованием TPI вместо сокетов (сокетная версия представлена в листинге 1.1). Если провести аналогию с языками программирования, то использование XTI или сокетов можно сравнить с программированием на языках высокого уровня, таких как С или Pascal, а непосредственно TPI — с программированием на ассемблере. Мы не являемся сторонниками непосредственного использования TPI в реальной жизни. Но понимание того, как работает TPI, и написание примера с использованием этого протокола позволит нам глубже понять, как работает библиотека сокетов в потоковой среде.
В листинге 31.1[1] показан наш заголовочный файл tpi_daytime.h.
Листинг 31.1. Наш заголовочный файл tpi_daytime.h
//streams/tpi_daytime.h
1 #include "unpxti.h"
2 #include <sys/stream.h>
3 #include <sys/tihdr.h>
4 void tpi_bind(int, const void*, size_t);
5 void tpi_connect(int, const void*, size_t);
6 ssize_t tpi_read(int, void*, size_t);
7 void tpi_close(int);
Нам нужно включить еще один дополнительный заголовочный файл помимо <sys/tihdr.h>, содержащего определения структур для всех сообщений TPI.
Листинг 31.2. Функция main для нашего клиента времени и даты с использованием TPI
//streams/tpi_daytime.c
1 #include "tpi_daytime.h"
2 int
3 main(int argc, char **argv)
4 {
5 int fd, n;
6 char recvline[MAXLINE + 1];
7 struct sockaddr_in myaddr, servaddr;
8 if (argc != 2)
9 err_quit("usage: tpi_daytime <Ipaddress>");
10 fd = Open(XTI_TCP, O_RDWR, 0);
11 /* связываем произвольный локальный адрес */
12 bzero(&myaddr, sizeof(myaddr));
13 myaddr.sin_family = AF_INET;
14 myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
15 myaddr.sin_port = htons(0);
16 tpi_bind(fd, &myaddr, sizeof(struct sockaddr_in));
17 /* заполняем адрес сервера */
18 bzero(&servaddr, sizeof(servaddr));
19 servaddr.sin_family = AF_INET;
20 servaddr.sin_port = htons(13); /* сервер времени и даты */
21 Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
22 tpi_connect(fd, &servaddr, sizeof(struct sockaddr_in));
23 for (;;) {
24 if ((n = tpi_read(fd, recvline, MAXLINE)) <= 0) {
25 if (n == 0)
26 break;
27 else
28 err_sys("tpi_read error");
29 }
30 recvline[n] = 0; /* завершающий нуль */
31 fputs(recvline, stdout);
32 }
33 tpi_close(fd);
34 exit(0);
35 }
Открытие транспортного устройства, связывание локального адреса10-16 Мы открываем устройство, соответствующее поставщику транспортных служб (обычно /dev/tcp). Мы заполняем структуру адреса сокета Интернета значениями INADDR_ANY и 0 (для порта), указывая тем самым TCP связать произвольный локальный адрес с нашей точкой доступа. Мы вызываем свою собственную функцию tpi_bind (которая будет приведена чуть ниже) для выполнения этого связывания.
Заполнение структуры адреса сервера, установление соединения17-22 Мы заполняем другую структуру адреса сокета Интернета, внося в нее IP-адрес сервера (из командной строки) и порт (13). Мы вызываем нашу функцию tpi_connect для установления соединения.
Считывание данных с сервера, копирование в стандартный поток вывода23-33 Как и в случае других клиентов времени и даты, мы просто копируем данные, пришедшие по соединению, в стандартный поток вывода, останавливаясь при получении признака конца файла, присланного сервером (например, сегмент FIN). Мы сделали этот цикл похожим на тот, который использовался в коде сокетного клиента (см. листинг 1.1), поскольку наша функция tpi_read при нормальном завершении соединения на стороне сервера будет возвращать нулевое значение. Затем мы вызываем нашу функцию tpi_close для того, чтобы закрыть эту точку доступа.
Наша функция tpi_bind показана в листинге 31.3.
Листинг 31.3. Функция tpi_bind: связывание локального адреса с точкой доступа
//streams/tpi_bind.c
1 #include "tpi_daytime.h"
2 void
3 tpi_bind(int fd, const void *addr, size_t addrlen)
4 {
5 struct {
6 struct T_bind_req msg_hdr;
7 char addr[128];
8 } bind_req;
9 struct {
10 struct T_bind_ack msg_hdr;
11 char addr[128];
12 } bind_ack;
13 struct strbuf ctlbuf;
14 struct T_error_ack *error_ack;
15 int flags;
16 bind_req.msg_hdr.PRIM_type = T_BIND_REQ;
17 bind_req.msg_hdr.ADDR_length = addrlen;
18 bind_req.msg_hdr.ADDR_offset = sizeof(struct T_bind_req);
19 bind_req.msg_hdr.CONIND_number = 0;
20 memcpy(bind_req.addr, addr, addrlen); /* sockaddr_in{} */
21 ctlbuf.len = sizeof(struct T_bind_req) + addrlen;
22 ctlbuf.buf = (char*)&bind_req;
23 Putmsg(fd, &ctlbuf, NULL, 0);
24 ctlbuf.maxlen = sizeof(bind_ack);
25 ctlbuf.len = 0;
26 ctlbuf.buf = (char*)&bind_ack;
27 flags = RS_HIPRI;
28 Getmsg(fd, &ctlbuf, NULL, &flags);
29 if (ctlbuf.len < (int)sizeof(long))
30 err_quit("bad length from getmsg");
31 switch (bind_ack.msg_hdr.PRIM_type) {
32 case T_BIND_ACK:
33 return;
34 case T_ERROR_ACK:
35 if (ctlbuf.len < (int)sizeof(struct T_error_ack))
36 err_quit("bad length for T_ERROR_ACK");
37 error_ack = (struct T_error_ack*)&bind_ack.msg_hdr;
38 err_quit("T_ERROR_ACK from bind (%d, %d)",
39 error_ack->TLI_error, error_ack->UNIX_error);
40 default:
41 err_quit("unexpected message type: %d", bind_ack.msg_hdr.PRlM_type);
42 }
43 }
Заполнение структуры T_bind_req16-20 Заголовочный файл <sys/tihdr.h> определяет структуру T_bind_req:
struct T_bind_req {
long PRIM_type; /* T_BIND_REQ */
long ADDR_length; /* длина адреса */
long ADDR_offset; /* смещение адреса */
unsigned long CONIND_number; /* сообщения о соединении */
/* далее следует адрес протокола для связывания */
};
Все запросы TPI определяются как структуры, начинающиеся с поля типа long. Мы определяем свою собственную структуру bind_req, начинающуюся со структуры T_bind_req, после которой располагается буфер, содержащий локальный адрес для связывания. TPI ничего не говорит о содержимом буфера — оно определяется поставщиком. Поставщик TCP предполагает, что этот буфер содержит структуру sockaddr_in.
Мы заполняем структуру T_bind_req, устанавливая элемент ADDR_length равным размеру адреса (16 байт для структуры адреса сокета Интернета), а элемент ADDR_offset — равным байтовому сдвигу адреса (он следует непосредственно за структурой T_bind_req). У нас нет гарантии, что это местоположение соответствующим образом выровнено для записи структуры sockaddr_in, поэтому мы вызываем функцию memcpy, чтобы скопировать структуру вызывающего процесса в нашу структуру bind_req. Мы присваиваем элементу CONIND_number нулевое значение, потому что мы находимся на стороне клиента, а не на стороне сервера.
Вызов функции putmsg21-23 TPI требует, чтобы только что созданная нами структура была передана поставщику как одно сообщение M_PROTO. Следовательно, мы вызываем функцию putmsg, задавая структуру bind_req в качестве управляющей информации, без каких-либо данных и с флагом 0.
Вызов функции getmsg для чтения сообщений с высоким приоритетом24-30 Ответом на наш запрос T_BIND_REQ будет либо сообщение T_BIND_ACK, либо сообщение T_ERROR_ACK. Сообщения, содержащие подтверждение, отправляются как сообщения с высоким приоритетом (M_PCPROTO), так что мы считываем их при помощи функции getmsg с флагом RS_HIPRI. Поскольку ответ является сообщением с высоким приоритетом, он получает преимущество перед всеми обычными сообщениями в потоке.
Эти два сообщения выглядят следующим образом:
struct T_bind_ack {
long PRIM_type; /* T_BIND_ACK */
long ADDR_length; /* длина адреса */
long ADDR_offset; /* смещение адреса */
unsigned long CONIND_number; /* индекс подключения для помещения
в очередь */
};
/* затем следует связанный адрес */
struct T_error_ack {
long PRIM_type; /* T_ERROR_ACK */
long ERROR_prim; /* примитивная ошибка ввода */
long TLI_error; /* код ошибки TLI */
long UNIX_error; /* код ошибки UNIX */
};
В начале каждого сообщения указан его тип, так что мы можем начать считывать ответ, предполагая, что это сообщение T_BIND_ACK, а затем, прочитав его тип, обрабатывать его тем или иным способом. Мы не ждем никаких данных от поставщика, поэтому третий аргумент функции getmsg мы задаем как пустой указатель.