Чтение онлайн

ЖАНРЫ

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

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. Обработка ошибок: функции-обертки

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

socket
,
inet_pton
,
connect
,
read
и
fputs
, и когда ошибка случается, мы вызываем свои собственные функции
err_quit
и
err_sys
для печати сообщения об ошибке и для прерывания выполнения программы. В отдельных случаях, когда функция возвращает ошибку, бывает нужно сделать еще что-либо помимо прерывания программы, как показано в листинге 5.9, когда мы должны проверить прерванный системный вызов.

Поскольку прерывание программы из-за ошибки — типичное явление, мы сократим наши программы, определив функции-обертки, которые будут вызывать соответствующие рабочие функции, проверять возвращаемые значения и прерывать программу при возникновении ошибки. Соглашение, используемое нами, заключается в том, что название функции-обертки пишется с заглавной буквы, например:

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).

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

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.

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

Значение системной переменной Unix errno

Когда при выполнении функции Unix (например, одной из функций сокетов) происходит ошибка, глобальной переменной

errno
присваивается положительное значение, указывающее на тип ошибки, а возвращаемое значение функции обычно равно -1. Наша функция
err_sys
проверяет значение переменной
errno
и печатает строку с соответствующим сообщением об ошибке (например, «Время соединения истекло», если значение переменной errno равно
ETIMEDOUT
).

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

<sys/errno.h>
. Ни одна ошибка не имеет кода 0.

Переменную errno нельзя хранить как глобальную переменную в случае множества потоков, у которых все глобальные переменные являются общими. О решении этой проблемы мы расскажем в главе 23.

Поделиться с друзьями: