Чтение онлайн

ЖАНРЫ

UNIX: разработка сетевых приложений
Шрифт:

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); /* на всякий случай */

Поделиться с друзьями: