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

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

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

Сравнение времени выполнения различных версий функции str_cli

Итак, мы продемонстрировали четыре различных версии функции str_cli. Для каждой версии мы покажем время, которое потребовалось для ее выполнения, в том числе и для версии, использующей программные потоки (см. листинг 26.1). В каждом случае было скопировано 2000 строк от клиента Solaris к серверу с периодом RTT, равным 175 мс:

■ 354,0 с, режим остановки и ожидания (см. листинг 5.4);

■ 12,3 с, функция select и блокируемый ввод-вывод (см. листинг 6.2);

■ 6,9 с, неблокируемый ввод-вывод (см. листинг 16.1);

■ 8,7 с, функция fork (см. листинг 16.6);

■ 8,5 с, версия с потоками (см. листинг 26.1).

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

16.3. Неблокируемая функция connect

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

1. Трехэтапное рукопожатие может наложиться на какой-либо другой процесс. Для выполнения функции connect требуется один период обращения RTT (см. раздел 2.5), и это может занять от нескольких миллисекунд в локальной сети до сотен миллисекунд или нескольких секунд в глобальной сети. Это время мы можем провести с пользой, выполняя какой-либо другой процесс.

2. Мы можем установить множество соединений одновременно, используя эту технологию. Этот способ уже стал популярен в применении к веб-браузерам, и такой пример мы приводим в разделе 16.5.

3. Поскольку мы ждем завершения установления соединения с помощью функции select, мы можем задать предел времени для функции select, что позволит нам сократить тайм-аут для функции connect. Во многих реализациях тайм-аут функции connect лежит в пределах от 75 с до нескольких минут. Бывают случаи, когда приложению нужен более короткий тайм-аут, и одним из решений может стать использование неблокируемой функции connect. В разделе 14.2 рассматриваются другие способы помещения тайм-аута в операции с сокетами.

Как бы просто ни выглядела неблокируемая функция connect, есть ряд моментов, которые следует учитывать.

■ Даже если сокет является неблокируемым, то когда сервер, с которым мы соединяемся, находится на том же узле, обычно установление соединения происходит немедленно при вызове функции connect.

■ В Беркли-реализациях (а также POSIX) имеются два следующих правила, относящихся к функции select и неблокируемой функции connect: во-первых, когда соединение устанавливается успешно, дескриптор становится готовым для записи [128, с. 531], и во-вторых, когда при установлении соединения встречается ошибка, дескриптор становится готовым как для чтения, так и для записи [128, с. 530].

ПРИМЕЧАНИЕ

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

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

16.4. Неблокируемая функция connect: клиент времени и даты

В листинге 16.7 показана наша функция connect_nonb, вызывающая неблокируемую функцию connect. Мы заменяем вызов функции connect, имеющийся в листинге 1.1, следующим фрагментом кода:

if (connect_nonb(sockfd, (SA*)&servaddr, sizeof(servaddr), 0) < 0)

err_sys("connect error");

Первые три аргумента являются обычными аргументами функции connect, а четвертый аргумент — это число секунд, в течение которых мы ждем завершения установления соединения. Нулевое значение подразумевает отсутствие тайм- аута для функции select; следовательно, для установления соединения TCP ядро будет использовать свой обычный тайм-аут.

Листинг 16.7. Неблокируемая функция connect

//lib/connect_nonb.c

 1 #include "unp.h"


 2 int

 3 connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec)

 4 {

 5  int flags, n, error;

 6  socklen_t len;

 7  fd_set rset, wset;

 8  struct timeval tval;


 9  flags = Fcntl(sockfd, F_GETFL, 0);

10  Fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);


11  error = 0;

12  if ((n = connect(sockfd, saptr, salen)) < 0)

13   if (errno != EINPROGRESS)

14    return (-1);


15  /* Пока соединение устанавливается, мы можем заняться чем-то другим */


16  if (n == 0)

17   goto done; /* функция connect завершилась немедленно */


18  FD_ZERO(&rset);

19  FDSET(sockfd, &rset);

20  wset = rset;

21  tval.tv_sec = nsec;

22  tval.tv_usec = 0;


23  if ((n = Select(sockfd + 1, &rset, &wset, NULL,

24   nsec ? &tval : NULL)) == 0) {

25   close(sockfd); /* тайм-аут */

26   errno = ETIMEDOUT;

27   return (-1);

28  }

29  if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {

30   len = sizeof(error);

31   if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)

32    return (-1); /*в Solaris ошибка, ожидающая обработки */

33  } else

34   err_quit("select error: sockfd not set");


35 done:

36  Fcntl(sockfd, F_SETFL, flags); /* восстанавливаем флаги, задающие статус файла */


37  if (error) {

38   close(sockfd); /* на всякий случай */

39   errno = error;

40   return (-1);

41  }

42  return (0);

43 }

Задание неблокируемого сокета

9-10 Мы вызываем функцию fcntl, которая делает сокет неблокируемым.

11-14 Мы вызываем неблокируемую функцию connect. Ошибка, которую мы ожидаем (EINPROGRESS), указывает на то, что установление соединения началось, но еще не завершилось [128, с. 466]. Любая другая ошибка возвращается вызывающему процессу.

Выполнение других процессов во время установления соединения

15 На этом этапе мы можем делать все, что захотим, ожидая завершения установления соединения.

Проверка немедленного завершения

16-17 Если неблокируемая функция connect возвратила нуль, установление соединения завершилось. Как мы сказали, это может произойти, когда сервер находится на том же узле, что и клиент.

Вызов функции select

18-24 Мы вызываем функцию select и ждем, когда сокет будет готов либо для чтения, либо для записи. Мы обнуляем rset, включаем бит, соответствующий sockfd в этом наборе дескрипторов и затем копируем rset в wset. Это присваивание, возможно, является структурным присваиванием, поскольку обычно наборы дескрипторов представляются как структуры. Далее мы инициализируем структуру timeval и затем вызываем функцию select. Если вызывающий процесс задает четвертый аргумент нулевым (что соответствует использованию тайм-аута по умолчанию), следует задать в качестве последнего аргумента функции select пустой указатель, а не структуру timeval с нулевым значением (означающим, что мы не ждем вообще).

Обработка тайм-аутов

25-28 Если функция select возвращает нуль, это означает, что время таймера истекло, и мы возвращаем вызывающему процессу ошибку ETIMEDOUT. Мы также закрываем сокет, чтобы трехэтапное рукопожатие не продолжалось.

Проверка возможности чтения или записи

29-34 Если дескриптор готов для чтения или для записи, мы вызываем функцию getsockopt, чтобы получить ошибку сокета (SO_ERROR), ожидающую обработки. Если соединение завершилось успешно, это значение будет нулевым. Если при установлении соединения произошла ошибка, это значение является значением переменной errno, соответствующей ошибке соединения (например, ECONNREFUSED, ETIMEDOUT и т.д.). Мы также сталкиваемся с нашей первой проблемой переносимости. Если происходит ошибка, Беркли-реализации функции getsockopt возвращают нуль, а ошибка, ожидающая обработки, возвращается в нашей переменной error. Но в системе Solaris сама функция getsockopt возвращает -1, а переменная errno при этом принимает значение, соответствующее ошибке, ожидающей обработки. В нашем коде обрабатываются оба сценария.

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