16.4. Неблокируемая функция 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 при этом принимает значение, соответствующее ошибке, ожидающей обработки. В нашем коде обрабатываются оба сценария.

Восстановление возможности блокировки сокета и завершение

36-42 Мы восстанавливаем флаги, задающие статус файла, и возвращаемся. Если наша переменная errno имеет ненулевое значение в результате выполнения функции getsockopt, это значение хранится в переменной errno, и функция возвращает -1.

Как мы сказали ранее, проблемы переносимости для функции connect связаны с различными реализациями сокетов и отключения блокировки. Во-первых, возможно, что установление соединения завершится и придут данные для собеседника до того, как будет вызвана функция select. В этом случае сокет будет готов для чтения и для записи при успешном выполнении функции, как и при неудачном установленном соединении. В нашем коде, показанном в листинге 16.7, этот сценарий обрабатывается при помощи вызова функции getsockopt и проверки на наличие ошибки, ожидающей обработки, для сокета.

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

1. Вызвать функцию getpeername вместо функции getsockopt. Если этот вызов окажется неудачным и возвратится ошибка ENOTCONN, значит, соединение не было установлено, и чтобы получить ошибку, ожидающую обработки, следует вызвать для сокета функцию getsockopt с SO_ERROR.

2. Вызвать функцию read с нулевым значением аргумента length. Если выполнение функции read окажется неудачным, функция connect выполнилась неудачно, и переменная errno из функции read при этом указывает на причину неудачной попытки установления соединения. Если соединение успешно установлено, функция read возвращает нуль.

3. Снова вызвать функцию connect. Этот вызов окажется неудачным, и если ошибка — EISCONN, сокет уже присоединен, а значит, первое соединение завершилось успешно.

К сожалению, неблокируемая функция connect — это одна из самых сложных областей сетевого программирования с точки зрения переносимости. Будьте готовы к проблемам совместимости, особенно с более ранними реализациями. Более простой технологией является создание потока (см. главу 26) для обработки соединения.

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

Настройка даты и времени

Из книги Работа на ноутбуке автора Садовский Алексей

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


Настройка даты и времени

Из книги Компьютер на 100. Начинаем с Windows Vista автора Зозуля Юрий

Настройка даты и времени Для настройки даты и времени щелкните правой кнопкой мыши на значке часов в области уведомлений и выберите пункт Настройка даты/времени. Чтобы изменить показания часов или текущую дату, вам понадобятся полномочия администратора компьютера.


7.10. Контроль даты и времени

Из книги Программирование на языке Ruby [Идеология языка, теория и практика применения] автора Фултон Хэл

7.10. Контроль даты и времени В разделе 7.5 было показано, что стандартные функции не проверяют корректность даты, а «переносят» ее вперед, если необходимо. Например, 31 ноября становится 1 декабря.Иногда такое поведение даже желательно. А если нет, то спешу обрадовать:


Функции даты и времени

Из книги Справочник по PHP автора

Функции даты и времени checkdateПроверяет правильность даты/времени.Синтаксис:int checkdate(int month, int day, int year);Функция checkdate() проверяет правильность даты, заданной в ее аргументах.Возвращает true если дата, указанная как "month, day, year" (месяц, число, год), правильна, иначе false. Дата считается


18.1. Вывод времени и даты

Из книги Разработка приложений в среде Linux. Второе издание автора Джонсон Майкл К.

18.1. Вывод времени и даты 18.1.1. Представление времени В системах Unix и Linux время отслеживается в секундах до или после начала эпохи, которое определяется как полночь 1 января 1970 года по UTC[148]. Положительные значения времени относятся к периоду после начала эпохи;


3.4. Функции даты и времени

Из книги MySQL 5.0. Библиотека программиста автора Гольцман Виктор Иосифович

3.4. Функции даты и времени В данном разделе мы рассмотрим некоторые полезные функции, выполняющие различные операции с датами: получение текущей даты и/или времени, получение отдельных компонентов даты и/или времени, арифметические операции с датами (сложение, вычитание)


1.2. Простой клиент времени и даты

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

1.2. Простой клиент времени и даты Рассмотрим конкретный пример, на котором мы введем многие понятия и термины, используемые в этой книге. В листинге 1.1[1] представлена реализация TCP-клиента времени и даты. Этот клиент устанавливает TCP-соединение с сервером, а сервер просто


4.3. Функция connect

Из книги автора

4.3. Функция connect Функция connect используется клиентом TCP для установления соединения с сервером TCP.#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);Возвращает: 0 в случае успешного выполнения функции, -1 в случае ошибкиАргумент sockfd — это дескриптор сокета,


8.11. Функция connect для UDP

Из книги автора

8.11. Функция connect для UDP В конце разделе 8.9 мы упомянули, что асинхронные ошибки не возвращаются на сокете UDP, если сокет не был присоединен. На самом деле мы можем вызвать функцию connect для сокета UDP (см. раздел 4.3). Но это не приведет ни к чему похожему на соединение TCP: здесь не


Пример: клиент времени и даты

Из книги автора

Пример: клиент времени и даты В листинге 11.5 показан наш клиент времени и даты из листинга 1.1, переписанный с использованием функции tcp_connect.Листинг 11.5. Клиент времени и даты, переписанный с использованием функции tcp_connect//names/daytimetcpcli.c 1 #include "unp.h" 2 int 3 main(int argc, char **argv) 4 { 5  int


Пример: не зависящий от протокола UDP-клиент времени и даты

Из книги автора

Пример: не зависящий от протокола UDP-клиент времени и даты Теперь мы перепишем наш клиент времени и даты, показанный в листинге 11.3, так, чтобы в нем использовалась наша функция udp_client. В листинге 11.10 представлен не зависящий от протокола исходный код.Листинг 11.10. UDP-клиент


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

Из книги автора

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


Прерванная функция connect

Из книги автора

Прерванная функция connect Что происходит, если наш вызов функции connect на обычном блокируемом сокете прерывается, скажем, перехваченным сигналом, прежде чем завершится трехэтапное рукопожатие TCP? Если предположить, что функция connect не перезапускается автоматически, то она


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

Из книги автора

16.6. Неблокируемая функция accept Как было сказано в главе 6, функция select сообщает, что прослушиваемый сокет готов для чтения, когда установленное соединение готово к обработке функцией accept. Следовательно, если мы используем функцию select для определения готовности входящих