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

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

Читать книгу Уильям Стивенс - UNIX: разработка сетевых приложений, Уильям Стивенс . Жанр: Программное обеспечение.
Уильям Стивенс - UNIX: разработка сетевых приложений
Название: UNIX: разработка сетевых приложений
ISBN: -
Год: -
Дата добавления: 3 июль 2019
Количество просмотров: 296
(18+) Внимание! Книга может содержать контент только для совершеннолетних. Для несовершеннолетних просмотр данного контента СТРОГО ЗАПРЕЩЕН! Если в книге присутствует наличие пропаганды ЛГБТ и другого, запрещенного контента - просьба написать на почту для удаления материала.
Читать онлайн

UNIX: разработка сетевых приложений читать книгу онлайн

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

 9 int

10 main(int argc, char **argv)

11 {

12  int c;

13  struct addrinfo *ai;

14  char *h;

15  opterr = 0; /* отключаем запись сообщений getopt() в stderr */

16  while ((с = getopt(argc, argv, "v")) != -1) {

17   switch (c) {

18   case 'v':

19    verbose++;

20    break;

21   case '?':

22    err_quit("unrecognized option %c", c);

23   }

24  }

25  if (optind != argc-1)

26   err_quit("usage: ping [ -v ] <hostname>");

27  host = argv[optind];

28  pid = getpid() & 0xffff; /* поле идентификатора ICMP имеет размер 16 бит */

29  Signal(SIGALRM, sig_alrm);

30  ai = Host_serv(host, NULL, 0, 0);

31  h = Sock_ntop_host(ai->ai_addr, ai->ai_addrlen);

32  printf("PING %s (%s): %d data bytesn",

33  ai->ai_canonname ? ai->ai_canonname : h, h, datalen);

34  /* инициализация в соответствии с протоколом */

35  if (ai->ai_family == AF_INET) {

36   pr = &proto_v4;

37 #ifdef IPV6

38  } else if (ai->ai_family == AF_INET6) {

39   pr = &proto_v6;

40   if (IN6_IS_ADDR_V4MAPPED(&(((struct sockaddr_in6*)

41    ai->ai_addr)->sin6_addr)))

42    err_quit("cannot ping IPv4-mapped IPv6 address");

43 #endif

44  } else

45   err_quit("unknown address family %d", ai->ai_family);

46  pr->sasend = ai->ai_addr;

47  pr->sarecv = Calloc(1, ai->ai_addrlen);

48  pr->salen = ai->ai_addrlen;

49  readloop();

50  exit(0);

51 }

Определение структуры proto для IPv4 и IPv6

2-7 Определяется структура proto для IPv4 и IPv6. Указатели структуры адреса сокета инициализируются как нулевые, поскольку еще не известно, какая из версий будет использоваться — IPv4 или IPv6.

Длина дополнительных данных

8 Устанавливается количество дополнительных данных (56 байт), которые будут посылаться с эхо-запросом ICMP. При этом полная IPv4-дейтаграмма будет иметь размер 84 байта (20 байт на IPv4-заголовок и 8 байт на ICMP-заголовок), а IPv6-дейтаграмма будет иметь длину 104 байта. Все данные, посылаемые с эхо- запросом, должны быть возвращены в эхо-ответе. Время отправки эхо-запроса будет сохраняться в первых 8 байтах области данных, а затем, при получении эхо- ответа, будет использоваться для вычисления и вывода времени RTT.

Обработка параметров командной строки

15-24 Единственный параметр командной строки, поддерживаемый в нашей версии, это параметр -v, в результате использования которого большинство ICMP-сообщений будут выводиться на консоль. (Мы не выводим эхо-ответы, принадлежащие другой запущенной копии программы ping.) Для сигнала SIGALRM устанавливается обработчик, и мы увидим, что этот сигнал генерируется один раз в секунду и вызывает отправку эхо-запросов ICMP.

Обработка аргумента, содержащего имя узла

31-48 Строка, содержащая имя узла или IP-адрес, является обязательным аргументом и обрабатывается функцией host_serv. Возвращаемая структура addrinfo содержит семейство протоколов — либо AF_INET, либо AF_INET6. Глобальный указатель pr устанавливается на требуемую в конкретной ситуации структуру proto. Также с помощью вызова функции IN6_IS_ADDR_V4MAPPED мы убеждаемся, что адрес IPv6 на самом деле не является адресом IPv4, преобразованным к виду IPv6, поскольку даже если возвращаемый адрес является адресом IPv6, узлу будет отправлен пакет IPv4. (Если такая ситуация возникнет, можно переключиться и использовать IPv4.) Структура адреса сокета, уже размещенная в памяти с помощью функции getaddrinfo, используется для отправки, а другая структура адреса сокета того же размера размещается в памяти для получения.

Обработка ответов осуществляется функцией readlоор, представленной в листинге 28.4.

Листинг 28.4. Функция readloop

//ping/readlоор.c

 1 #include "ping.h"

 2 void

 3 readloop(void)

 4 {

 5  int size;

 6  char recvbuf[BUFSIZE];

 7  char controlbuf[BUFSIZE];

 8  struct msghdr msg;

 9  struct iovec iov;

10  ssize_t n;

11  struct timeval tval;

12  sockfd = Socket(pr->sasend->sa_family, SOCK_RAW, pr->icmpproto);

13  setuid(getuid()); /* права привилегированного пользователя

                         больше не нужны */

14  if (pr->finit)

15   (*pr->finit)();

16  size = 60 * 1024; /* setsockopt может завершиться с ошибкой */

17  setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));

18  sig_alrm(SIGALRM); /* отправка первого пакета */

19  iov.iov_base = recvbuf;

20  iov.iov_len = sizeof(recvbuf);

21  msg.msg_name = pr->sarecv;

22  msg.msg_iov = &iov;

23  msg.msg_iovlen = 1;

24  msg.msg_control = controlbuf;

25  for (;;) {

26   msg.msg_namelen = pr->salen;

27   msg.msg_controllen = sizeof(controlbuf);

28   n = recvmsg(sockfd, &msg, 0);

29   if (n < 0) {

30    if (errno == EINTR)

31     continue;

32    else

33     err_sys("recvmsg error");

24   }

35   Gettimeofday(&tval, NULL);

36   (*pr->fproc)(recvbuf, n, &msg, &tval);

37  }

38 }

Создание сокета

12-13 Создается символьный сокет, соответствующий выбранному протоколу. В вызове функции setuid нашему эффективному идентификатору пользователя присваивается фактический идентификатор пользователя. Для создания символьных сокетов программа должна иметь права привилегированного пользователя, но когда символьный сокет уже создан, от этих прав можно отказаться. Всегда разумнее отказаться от лишних прав, если в них нет необходимости, например на тот случай, если в программе есть скрытая ошибка, которой кто-либо может воспользоваться.

Выполнение инициализации для протокола

14-15 Мы выполняем функцию инициализации для выбранного протокола. Для IPv6 такая функция представлена в листинге 28.7.

Установка размера приемного буфера сокета

16-17 Пытаемся установить размер приемного буфера сокета, равный 61 440 байт (60×1024) — этот размер больше задаваемого по умолчанию. Это делается в расчете на случай, когда пользователь проверяет качество связи с помощью программы ping, обращаясь либо к широковещательному адресу IPv4, либо к групповому адресу. В обоих случаях может быть получено большое количество ответов. Увеличивая размер буфера, мы уменьшаем вероятность того, что приемный буфер переполнится.

Отправка первого пакета

18 Запускаем обработчик сигнала, который, как мы увидим, посылает пакет и создает сигнал SIGALRM один раз в секунду. Обычно обработчик сигналов не запускается напрямую, как у нас, но это можно делать. Обработчик сигналов является обычной функцией языка С, просто в нормальных условиях он асинхронно запускается ядром.

Подготовка msghdr для recvmsg

19-24 Мы записываем значения в неизменяемые поля структур msghdr и iovec, которые будут передаваться функции recvmsg.

Бесконечный цикл для считывания всех ICMP-сообщений

25-37 Основной цикл программы является бесконечным циклом, считывающим все пакеты, возвращаемые на символьный сокет ICMP. Вызывается функция gettimeofday для регистрации времени получения пакета, а затем вызывается соответствующая функция протокола (proc_v4 или proc_v6) для обработки ICMP-сообщения.

В листинге 28.5 приведена функция tv_sub, вычисляющая разность двух структур timeval и сохраняющая результат в первой из них.

Листинг 28.5. Функция tv_sub: вычитание двух структур timeval

//lib.tv_sub.c

 1 #include "unp.h"

 2 void

 3 tv_sub(struct timeval *out, struct timeval *in)

 4 {

 5  if ((out->tv_usec -= in->tv_usec) < 0) { /* out -= in */

 6   --out->tv_sec;

 7   out->tv_usec += 1000000;

 8  }

 9  out->tv_sec -= in->tv_sec;

10 }

В листинге 28.6 приведена функция proc_v4, обрабатывающая все принимаемые сообщения ICMPv4. Можно также обратиться к рис. А.1, на котором изображен формат заголовка IPv4. Кроме того, следует осознавать, что к тому моменту, когда процесс получает на символьном сокете ICMP-сообщение, ядро уже проверило, что основные поля в заголовке IPv4 и в сообщении ICMPv4 действительны [128, с. 214, с. 311].

Листинг 28.6. Функция proc_v4: обработка сообщений ICMPv4

//ping/prov_v4.c

 1 #include "ping.h"

 2 void

 3 proc_v4(char *ptr, ssize_t len, struct msghdr *msg, struct timeval *tvrecv)

 4 {

 5  int hlen1, icmplen;

 6  double rtt;

 7  struct ip *ip;

 8  struct icmp *icmp;

Перейти на страницу:
Комментарии (0)