Как уже говорилось, пока мы лишь выделили наиболее важные моменты, детальным исследованием которых займемся в дальнейшем.
1.3. Независимость от протокола
зависимой от протокола protocol dependentЕсли мы хотим изменить программу так, чтобы она работала по протоколу IPv6, мы должны изменить код. В листинге 1.2 показана новая версия программы с соответствующими изменениями, отмеченными полужирным шрифтом.
Листинг 1.2. Версия листинга 1.1 для IPv6
//intro/daytimetcpcliv6.с
1 #include "unp.h"
2 int
3 main(int argc, char **argv)
4 {
5 int sockfd, n;
6 char recvline[MAXLINE + 1];
7 struct sockaddr_in6servaddr;
8 if (argc != 2)
9 err_quit("usage: a.out <Ipaddress>");
10 if ((sockfd = socket( AF_INET6, SOCK_STREAM, 0)) < 0)
11 err_sys("socket error");
12 bzero(&servaddr, sizeof(servaddr));
13 servaddr. sin6_family= AF_INET6;
14 servaddr. sin6_port= htons(13); /* сервер времени и даты */
15 if (inet_pton( AF_INET6, argv[1], &servaddr. sin6_addr) <= 0)
16 err_quit("inet_pton error for %s", argv[1]);
17 if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) < 0)
18 err_sys("connect error");
19 while ((n = read(sockfd, recvline, MAXLINE)) > 0) {
20 recvline[n] = 0; /* символ конца строки */
21 if (fputs(recvline, stdout) == EOF)
22 err_sys("fputs error");
23 }
24 if (n < 0)
25 err_sys("read error");
26 exit(0);
27 }
Изменились только пять строк, но в результате мы все равно получили программу, зависимую от протокола, в данном случае от протокола IPv6. Лучше сделать программу независимой от протокола ( protocol independent ). В листинге 11.3 представлена независимая от протокола версия этого клиента, основанная на вызове getaddrinfoиз tcp_connect.
Другим недостатком наших программ является то, что пользователь должен вводить IP-адрес сервера в точечно-десятичной записи (например, 206.168.112.219 для версии IPv4). Людям проще работать с именами, чем с числами (например, www.unpbook.com). В главе 11 мы обсудим функции, обеспечивающие преобразование имен узлов в IP-адреса и имен служб в порты. Мы специально откладываем описание этих функций, продолжая использовать IP-адреса и номера портов, чтобы иметь ясное представление о том, что именно входит в структуры адресов сокетов, которые мы должны заполнить и проверить. Это также упрощает наши объяснения сетевого программирования, снимая необходимость описывать в подробностях еще один набор функций.
1.4. Обработка ошибок: функции-обертки
каждогоПоскольку прерывание программы из-за ошибки типичное явление, мы сократим наши программы, определив функции-обертки , которые будут вызывать соответствующие рабочие функции, проверять возвращаемые значения и прерывать программу при возникновении ошибки. Соглашение, используемое нами, заключается в том, что название функции-обертки пишется с заглавной буквы, например:
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
Наша функция-обертка для
функции socket показана в листинге 1.3.
Листинг 1.3. Наша функция-обертка для функции socket
//lib/wrapsock.c
172 int
173 Socket(int family, int type, int protocol)
174 {
175 int n;
176 if ((n = socket(family, type, protocol)) < 0)
177 err_sys("socket error");
178 return (n);
179 }
Хотя вы можете решить, что использование этих функций-оберток не обеспечивает большой экономии, на самом деле это не так. Обсуждая потоки (threads) в главе 26, мы обнаружим, что, когда происходит какая-либо ошибка, функции потоков не устанавливают значение стандартной переменной Unix errnoравным определенной константе, специфической для произошедшей ошибки. Вместо этого значение переменной errnoпросто возвращается функцией. Это значит, что каждый раз, когда мы вызываем одну из функций pthread, мы должны разместить в памяти переменную, сохранить возвращаемое значение в этой переменной и установить errnoравной этому значению перед вызовом err_sys. Чтобы избежать загромождения кода скобками, мы можем использовать оператор языка С запятая для объединения присваивания значения переменной errnoи вызова err_sysв отдельное выражение следующим образом:
int n;
if ((n = pthread_mutex_lock(&ndone_mutex)) != 0)
errno = n, err_sys("pthread_mutex_lock error");
ВНИМАНИЕВ тексте книги вам будут встречаться функции, имена которые начинаются с заглавной буквы. Это наши собственные функции-обертки. Функция-обертка вызывает функцию, имеющую такое же имя, но начинающееся со строчной буквы.При описании исходного кода, представленного в тексте книги, мы всегда ссылаемся на вызываемую функцию низшего уровня (например, socket), но не на функцию-обертку (например, Socket).
Pthread_mutex_lock(&ndone_mutex);
Листинг 1.4. Наша собственная функция-обертка для функции pthread_mutex_lock
//lib/wrappthread.c
72 void
73 Pthread_mutex_lock(pthread_mutex_t *mptr)
74 {
75 int n;
76 if ((n = pthread_mutex_lock(mptr)) == 0)
77 return;
78 errno = n;
79 err_sys("pthread_mutex_lock error");
80 }
ПРИМЕЧАНИЕЕсли аккуратно программировать на С, можно использовать макросы вместо функций, что обеспечивает небольшой выигрыш в производительности, однако функции- обертки редко, если вообще когда-нибудь бывают причиной недостаточной производительности программ.Наш выбор первая заглавная буква в названии функции является компромиссом. Было предложено множество других стилей: подстановка префикса e перед названием функции (как сделано в [67, с. 182]), добавление _е к имени функции и т.д. Наш вариант кажется наименее отвлекающим внимание и одновременно дающим визуальное указание на то, что вызывается какая-то другая функция.
Эта технология имеет, кроме того, полезный побочный эффект: она позволяет проверять возникновение ошибок при выполнении таких функций, ошибки в которых часто остаются незамеченными, например close и listen.