KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программное обеспечение » Уильям Стивенс - UNIX: разработка сетевых приложений

Уильям Стивенс - UNIX: разработка сетевых приложений

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Уильям Стивенс, "UNIX: разработка сетевых приложений" бесплатно, без регистрации.
Перейти на страницу:

Будем считать, что программа проверяет возвращаемое значение функции close и затем выводит значение переменной errno, если произошла ошибка, как в следующем примере:

if (close(fd) < 0) {

 fprintf(stderr, "close error, errno = $dn", errno);

 exit(1);

}

Существует небольшой промежуток времени между сохранением кода ошибки в переменной errno в тот момент, когда системный вызов возвращает управление, и выводом этого значения программой. В течение этого промежутка другой программный поток внутри процесса (то есть обработчик сигналов) может изменить значение переменной errno. Если, например, при вызове обработчика сигналов главный поток управления находится между close и fprintf и обработчик сигналов делает какой-то другой системный вызов, возвращающий ошибку (допустим, вызывается функция write), то значение переменной errno, записанное при вызове функции close, заменяется на значение, записанное при вызове функции write.

При рассмотрении этих двух проблем в связи с обработчиками сигналов одним из решений проблемы с функцией gethostbyname (возвращающей указатель на статическую переменную) будет не вызывать из обработчика сигнала функции, которые не допускают повторное вхождение. Проблемы с переменной errno (одна глобальная переменная, которая может быть изменена обработчиком сигнала) можно избежать, перекодировав обработчик сигнала так, чтобы он сохранял и восстанавливал значение переменной errno следующим образом:

void sig_alrm(int signo) {

int errno_save;


errno_save = errno; /* сохраняем значение этой переменной

                       при вхождении */

if (write( ... ) != nbytes)

 fprintf(stderr, "write error, errno = %dn", errno);

 errno = errno_save; /* восстанавливаем значение этой переменной

                        при завершении */

}

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

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

11.19. Функции gethostbyname_r и gethostbyaddr_r

Чтобы превратить функцию, не допускающую повторное вхождение, такую как gethostbyname, в повторно входимую, можно воспользоваться двумя способами.

1. Вместо заполнения и возвращения статической структуры вызывающий процесс размещает структуру в памяти, и функция, допускающая повторное вхождение, заполняет эту структуру. Эта технология используется для перехода от функции gethostbyname (которая не допускает повторное вхождение) к функции gethostbyname_r (которая допускает повторное вхождение). Но это решение усложняется, поскольку помимо того, что вызывающий процесс должен предоставить структуру hostent для заполнения, эта структура также указывает на другую информацию: каноническое имя, массив указателей на псевдонимы, строки псевдонимов, массив указателей на адреса и сами адреса (см., например, рис. 11.2). Вызывающий процесс должен предоставить один большой буфер, используемый для дополнительной информации, и заполняемая структура hostent будет содержать различные указатели на этот буфер. При этом добавляется как минимум три аргумента функции: указатель на заполняемую структуру hostent, указатель на буфер, используемый для всей прочей информации, и размер этого буфера. Требуется также четвертый дополнительный аргумент — указатель на целое число, в котором будет храниться код ошибки, поскольку глобальная целочисленная переменная h_errno больше не может использоваться. (Глобальная целочисленная переменная h_errno создает ту же проблему повторного вхождения, которая описана нами для переменной errno.)

Эта технология также используется функциями getnameinfo и inet_ntop.

2. Входящая функция вызывает функцию malloc и динамически выделяет память. Это технология, используемая функцией getaddrinfo. Проблема при таком подходе заключается в том, что приложение, вызывающее эту функцию, должно вызвать также функцию freeaddrinfo, чтобы освободить динамическую память. Если эта функция не вызывается, происходит утечка памяти: каждый раз, когда процесс вызывает функцию, выделяющую память, объем памяти, задействованной процессом, возрастает. Если процесс выполняется в течение длительного времени (что свойственно сетевым серверам), то потребление памяти этим процессом с течением времени неуклонно растет.

Обсудим функции Solaris 2.x, допускающие повторное вхождение, не используемые для сопоставления имен с адресами, и наоборот (то есть для разрешения имен).

#include <netdb.h>


struct hostent *gethostbyname_r(const char *hostname,

struct hostent *result, char *buf, int buflen, int *h_errnop);

struct hostent *gethostbyaddr_r(const char *addr, int len,

 int type, struct hostent *result, char *buf, int buflen,

 int *h_errnop);

Обе функции возвращают: непустой указатель в случае успешного выполнения, NULL в случае ошибки

Для каждой функции требуется четыре дополнительных аргумента. Аргумент result — это структура hostent, размещенная в памяти вызывающим процессом и заполняемая данной функцией. При успешном выполнении функции этот указатель также является возвращаемым значением.

Аргумент buf — это буфер, размещенный в памяти вызывающим процессом, a buflen — его размер. Буфер будет содержать каноническое имя, массив указателей на псевдонимы, строки псевдонимов, массив указателей на адреса и сами адреса. Все указатели в структуре hostent, на которую указывает result, указывают на этот буфер. Насколько большим должен быть этот буфер? К сожалению, все, что сказано в большинстве руководств, это что-то неопределенное вроде «Буфер должен быть достаточно большим, чтобы содержать все данные, связанные с записью узла». Текущие реализации функции gethostbyname могут возвращать до 35 указателей на альтернативные имена (псевдонимы), до 35 указателей на адреса и использовать буфер размером 8192 байт для хранения альтернативных имен (псевдонимов) и адресов. Поэтому буфер размером 8192 байт можно считать подходящим.

Если происходит ошибка, код ошибки возвращается через указатель h_errnop, а не через глобальную переменную h_errno.

ПРИМЕЧАНИЕ

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

Во-вторых, не существует стандарта для функций _r. В этом разделе (в качестве примера) мы привели две функции _r, предоставляемые Solaris 2.x. В Linux присутствуют аналогичные функции, возвращающие hostent в качестве аргумента типа значение-результат. В Digital Unix и HP-UX имеются версии этих функций с другими аргументами. Первые два аргумента функции gethostbyname_r такие же, как и в версии Solaris, но оставшиеся три аргумента версии Solaris объединены в новую структуру hostent_data (которая должна быть размещена в памяти вызывающим процессом), а указатель на эту структуру — это третий и последний аргумент. Обычные функции gethostbyname и gethostbyaddr в Digital Unix 4.0 и в HP-UX 10.30 допускают повторное вхождение при использовании собственных данных потоков (см. раздел 23.5). Интересный рассказ о разработке функций _r Solaris 2.x содержится в [70].

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

11.20. Устаревшие функции поиска адресов IPv6

В процессе разработки IPv6 интерфейс поиска адресов IPv6 много раз претерпевал серьезные изменения. В какой-то момент интерфейс был сочтен усложненным и недостаточно гибким, так что от него полностью отказались в RFC 2553 [38]. Документ RFC 2553 предлагал собственные функции, которые в RFC 3493 [36] были попросту заменены getaddrinfo и getnameinfo. В этом разделе мы вкратце рассмотрим старые интерфейсы на тот случай, если вам придется переписывать программы, использующие их.

Константа RES_USE_INET6

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*