Rambler's Top100 Service калинин.ru / программирование / сети /  << 05.11.00 >>

Определение ip-адреса по имени хоста, adns

Есть такой, характерный для организации "традиционного" UNIX'а, системный вызов под названием gethostbyname():

struct hostent *
gethostbyname(const char *name);

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

Собственно, обычно, для того, что бы получить IP-адрес по DNS-имени, используют именно этот вызов. Его недостатками являются уже упомянутая выше не "реентерабельность" (это английское слово, судя по всему, стало термином), отсутствие возможности определения адреса хоста, соответствующему иному протоколу, чем IPv4 и... низкая производительность, как это ни странно.

Надо понимать, что DNS, кроме самих серверов, включает в себя еще и протокол передачи данных, который принципиально ничем не отличается от других (того же HTTP). В предыдущей заметке в этом разделе я уже говорил о том, что программа, выкачивающая один файл, отличается от программы, которая выкачивает несколько файлов. Тоже самое и с обращениями к DNS-серверам: если "разрешение" одного хоста произойдет достаточно быстро (относительно), то в случае с большим количеством хостов все уже не так радужно.

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

Вообще говоря, конечно же, можно как-то попытаться ускорить процесс. Например, перед тем, как обратиться к DNS-серверу, gethostbyname() просматривает содержимое локального файла /etc/hosts... или, еще можно использовать тот факт, что DNS-сервер имеет свой кэш и, если подряд следуют два запроса на одинаковые хосты, ответ на второй запрос будет отдан клиенту из кэша. Таким образом, можно установить DNS-сервер (тот же named) на локальной машине, тогда все повторные запросы будут выполняться моментально.

В случае, когда все хосты известны заранее, можно разбить программу на две части: сначала происходит разрешение имен, а затем --- выкачивание информации. Но, если честно, это не выход.

Более логичное решение заключается в том, что, если программа управляется событиями ввода-вывода, то надо добавить сюда еще и события "определения IP-адреса". Это уже предполагает то, что gethostbyname() использовать больше нельзя: этот вызов блокирует процесс. Можно воспользоваться "реентерабельной" версией gethostbyname_r() и выделить несколько потоков в программе, которые будут постоянно ожидать завершения определения IP-адреса... только это... скорее всего опять несколько неправильно.
    Лирическое отступление:
   

Наверно, все-таки стоит об этом упомянуть здесь: потоки бывают трех типов: потоки ядра, реальные потоки и потоки пользовательские. Первые, понятно, работают внутри ядра и для его нужд; вторые --- то же самое, что и потоки ядра, но с доступом к данным процесса... А вот пользовательские потоки --- это библиотечная реализация потоков и может быть вообще никак не связана с реальными потоками в ОС. Те же pthreads --- на самом деле, пользовательские потоки; собственно, тем и хороши. Вполне вероятно, что по этой тематике я пройдусь когда-нибудь подробнее, но выделение DNS-ресолвера в отдельный пользовательский поток, это вполне здравая мысль. Если, конечно же, вы уверены в том, что сможете это корректно сделать.

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

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

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

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

#include <sys/types.h>
#include <stdio.h>
#include <signal.h>

#include <adns.h>

int main(int argc, char* argv[])
{
    Лирическое отступление:
   

Любая программа, которая работает с сетями, должна каким-либо образом обрабатывать сигнал SIGPIPE, обычно этот сигнал просто игнорируется. Все дело в том, что ядро сгенерирует его для вашей программы в случае обрыва свзязи (что является обычной ситуацией), а обработчик по-умолчанию просто прекратит работу вашей программы.

Библиотека ADNS, в принципе, сама устанавливает обработчик SIGPIPE в SIG_IGN, но это можно отключить :-)

  struct sigaction sigpipe, sigpipeo;
  
  sigemptyset(&sigpipe.sa_mask);
  sigpipe.sa_flags = SA_RESTART;
  sigpipe.sa_handler = SIG_IGN;
  sigaction(SIGPIPE, &sigpipe, &sigpipeo);
  
  adns_state ads = NULL;
  
  /*
   * Инициализация ADNS. 
   */
  adns_init(&ads, adns_if_nosigpipe, 0);

  // ...
  
  /*
   * Осуществляем все запросы к ADNS. В данном случае ---
   * пачкой и сразу, но можно и в процессе обработки.
   */
  for( ; ; )
    {
       // ...

       adns_query aquery = 0;

       /*
        * А вот документацию на эту функцию, скорее
        * всего, посмотреть придется, потому что тут
        * надо указать флаги, характеризующие то, что
        * вы хотите от ADNS получить. Под hostname
        * понимается название хоста.
        */
       adns_submit(ads, hostname,
                   (adns_rrtype)adns_r_addr,
                   (adns_queryflags)adns_qf_owner,
                   0,
                   &aquery);

       // ...
    }
  
  /*
   * Теперь все запросы поданы, надо обработать
   * результаты. В данном случае, запрос _ожидается_.
   * Но можно и просто проверять наличие выполненных
   * запросов.
   */
  for( ; ; )
    {
      adns_answer* answer = NULL;
      adns_query query = NULL;
      
      adns_wait_poll(ads, &query, &answer, NULL);
      
      if(!answer) break;
      
      if(answer->status == adns_s_ok)
        {
          /*
           * Ответ получен. Теперь в answer находится то,
           * что вы "заказали" при submit'е. В данном
           * случае имеют интерес поля answer->owner,
           * answer->rrs и answer->nrrs.
           */
        }
    }
  
  /*
   * Завершение работы. Об этом надо сказать ADNS.
   */
  adns_finish(ads);
  
  return 0;
}

Ресолвер очень быстрый. Желающие могут написать маленькую итеративную программу с использованием gethostbyname(), а потом попробовать утилиту, идущую в комплекте с ADNS, под названием adnshost, и сравнить количество затраченного времени на большом списке хостов. Кстати сказать, еще один из недостатков libwww заключается как раз в том, что работа с DNS в ней, в конце-концов, сводится именно к вызову gethostbyname().

Резюме

Использование gethostbyname в реальных программах обычно приводит к потере производительности. Для решения этой проблемы можно использовать "реентерабельную" версию этого же системного вызова, но лучше взять библиотеки, использующие асинхронные операции ввода-вывода для работы с DNS, например, adns.


Версия для печати


  Ссылки по теме:
http://www.chiark.greenend.org
   Официальная страница ADNS.
W. Richard Stevens
   Network programming, volume 1.
W. Richard Stevens
   TCP/IP illustrated, volume 1.
  Рядом в разделе:
Неблокирующий connect() (01.12.00)
   В продолжение темы о замене блокирующего вызова , хочется рассказать о другой функции интерфейса сокетов, . Она имеет следующий прототип: int...   >>>>
lingering close (29.10.00)
   Когда программа выкачивает один файл с удаленного сервера с использованием протокола TCP, а после этого сразу же "отваливается", то проблем, скорее...   >>>>
  Рядом по дате:
acm.gui.uva.es, задачник по программированию (07.11.00)
   Наконец-то я вспомнил о действительно полезном ресурсе, который стоит описать. Вообще, полезность или бесполезность чего-либо, конечно же, нельзя оценить "огульно", надо...   >>>>
Информационно-поисковые системы (02.11.00)
   Поисковые системы в интернете на сегодняшний день являются единственным способом доступа пользователя к информации, расположенной в сети. Я имею в виду...   >>>>
  Содержание:
Заглавная страница
Мой блог
Мое резюме
Дайджест
Программирование
   C&C++
Сети
Unix
Алгоритмы
Оптимизация
Соревнования
Отвлеченно
XML
TeX
Просто так
Студенческое
Туризм
  Байки
Фотографии
Комментарии
   Книги
Web-ресурсы
Фильмы
Интернет
Программное обеспечение
Жизнь
Благодарности
Форум
Хронология
 
  В этом разделе:
События ядра в FreeBSD. (16.07.01)
   Обработка большого количества сетевых соединений всегда затруднительна. Мало того, не существует стандартных решений, подходящих для проблем любого вида, в которых возникает...   >>>>
Обзор CORBA (14.01.01)
   CORBA (расшифровывается как Common Object Request Broker) это технология, которая позволяет рассматривать компоненты распределенной системы как объекты, отвечающие некоторым определенным интерфейсам....   >>>>
"Тонкий" клиент (19.12.00)
   В предыдущей заметке Gregory Liokumovich рассказывал о применении событийной модели WinSock для программирования сетевых приложений. На самом деле, как мне кажется,...   >>>>
Событийная модель в WinSock (12.12.00)
   Автором этого текста является Gregory Liokumovich ( ). Он любезно прислал мне этот текст, связанный с программированием сетей при помощи интерфейса...   >>>>
Неблокирующий connect() (01.12.00)
   В продолжение темы о замене блокирующего вызова , хочется рассказать о другой функции интерфейса сокетов, . Она имеет следующий прототип: int...   >>>>
Определение ip-адреса по имени хоста, adns (05.11.00)
   Есть такой, характерный для организации "традиционного" UNIX'а, системный вызов под названием : struct hostent * gethostbyname(const char *name); Традиционен он тем,...   >>>>
lingering close (29.10.00)
   Когда программа выкачивает один файл с удаленного сервера с использованием протокола TCP, а после этого сразу же "отваливается", то проблем, скорее...   >>>>
Содержание раздела полностью...
   Примерно в тоже время
acm.gui.uva.es, задачник по программированию (07.11.00)
   Наконец-то я вспомнил о действительно полезном ресурсе, который стоит описать. Вообще, полезность или бесполезность чего-либо, конечно же, нельзя оценить "огульно", надо...   >>>>
Информационно-поисковые системы (02.11.00)
   Поисковые системы в интернете на сегодняшний день являются единственным способом доступа пользователя к информации, расположенной в сети. Я имею в виду...   >>>>
Хронология полностью...
   Содержание
Заглавная страница
Мой блог
Мое резюме
Дайджест
Программирование
  C&C++
Сети
Unix
Алгоритмы
Оптимизация
Соревнования
Отвлеченно
XML
TeX
Туризм
  Байки
Фотографии
Комментарии
  Книги
Web-ресурсы
Фильмы
Интернет
Программное обеспечение
Жизнь
Студенческое
Просто так
Благодарности
Форум
Хронология
© 2000-2008, Andrey L. Kalinin
mailto:andrey@kalinin.ru
Rambler's Top100