Эффективное программирование TCP-IP

       

Использование select


Другой, более общий метод организации тайм-аута connect состоит в том, чтобы сделать сокет неблокирующим, а затем ожидать с помощью вызова select. При таком подходе удается избежать большинства трудностей, возникающих при попытке воспользоваться alarm, но остаются проблемы переносимости даже между разными UNIX-системами.

Сначала рассмотрим код установления соединения. В каркасе tcpclient.skel Модифицируйте функцию main, как показано в листинге 3.25.

Листинг 3.25. Прерывание connect по тайм-ауту с помощью select

connectto1.с

1    int main( int argc, char **argv )

2    {

3    fd_set rdevents;

4    fd_set wrevents;

5    fd_set exevents;

6    struct sockaddr_in peer;

7    struct timeval tv;

8    SOCKET s;

9    int flags;

10   int rc;

11   INIT();

12   set_address( argv[ 1 ], argv[ 2 ], &peer, "tcp" );



13   S = socket( AF_INET, SOCK_STREAM, 0 );

14   if ( !isvalidsock( s ) )

15     error( 1, errno, "ошибка вызова socket");

16   if( ( flags = fcntl( s, F_GETFL, 0 ) ) < 0 )

17     error( 1, errno, "ошибка вызова fcntl (F_GETFL)");

18   if ( fcntl( s, F_SETFL, flags | 0_NONBLOCK ) < 0 )

19     error( 1, errno, "ошибка вызова fcntl (F_SETFL)");

20   if ( ( rc = connect ( s, ( struct sockaddr * )&peer,

21     sizeoff peer ) ) ) && errno != EINPROGRESS )

22     error( 1, errno, "ошибка вызова connect" );

23   if ( rc == 0 )  /* Уже соединен? */

24   {

25   if ( fcntl( s, F_SETFL, flags ) < 0 )

26     error(1,errno,"ошибка вызова fcntl (восстановление флагов)”);

27     client( s, &peer );

28     EXIT( 0 );

29   }

30   FD_ZERO( &rdevents );

31   FD_SET( s, krdevents );

32   wrevents = rdevents;

33   exevents = rdevents;

34   tv.tv_sec = 5;

35   tv.tv_usec =0;

36   rc  =  select( s  + 1, &rdevents, &wrevents, &exevents, &tv );

37   if ( rc < 0 )

38     error( 1, errno, "ошибка вызова select" );

39   else if ( rc == 0 )

40     error( 1, 0, "истек тайм-аут connect\n" );


41   else if ( isconnected( s, &rdevents, &wrevents, kexevents ))

42   {

43     if (fcntl (s, F_SETFL, flags) < 0)

44      error(1,errno,"ошибка вызова fcntl(восстановление флагов)");

45     client( s, &peer );

46   }

47   else

48     error( 1, errno, "ошибка вызова connect");

49   EXIT( 0 );

50   }

Инициализация

16- 19 Получаем текущие флаги, установленные для сокета, с помощью операции OR, добавляем к ним флаг O_NONBLOCK и устанавливаем новые флаги.

Инициирование connect

20-29 Начинаем установление соединения с помощью вызова connect. Поскольку сокет помечен как неблокирующий, connect немедленно возвращает управление. Если соединение уже установлено (это возможно, если, например, вы соединялись с той машиной, на которой запущена программа), то connect вернет нуль, поэтому возвращаем сокет в режим блокирования и вызываем функцию client. Обычно в момент Возврата из connect соединение еще не установлено, и приходит код EINPROGRESS. Если возвращается другой код, то печатаем диагностическое сообщение и завершаем программу.

Вызов select

30-36 Подготавливаем, как обычно, данные для select и, в частности, устанавливаем тайм-аут на пять секунд. Также следует объявить заинтересованность в событиях исключения. Зачем - станет ясно позже.

Обработка код возврата select

37-40 Если select возвращает код ошибки или признак завершения по тайм-ауту, то выводим сообщение и заканчиваем работу. В случае ответа можно было бы, конечно, сделать что-то другое.

41-46 Вызываем функцию isconnected, чтобы проверить, удалось ли установить соединение. Если да, возвращаем сокет в режим блокирования и вызываем функцию client. Текст функции isconnected приведен в листингах 3.26 и 3.27.

4 7-48 Если соединение не установлено, выводим сообщение и завершаем сеанс.

К сожалению, в UNIX и в Windows применяются разные методы уведомления об успешной попытке соединения. Поэтому проверка вынесена в отдельную функцию. Сначала приводится UNIX-версия функции isconnected.



В UNIX, если соединение установлено, сокет доступен для записи. Если же произошла ошибка, то сокет будет доступен одновременно для записи и для чте­ния. Однако на это нельзя полагаться при проверке успешности соединения, по­скольку можно возвратиться из connect и получить первые данные еще до обращения к select. В таком случае сокет будет доступен и для чтения, и для записи -в точности, как при возникновении ошибки.

Листинг 3.26. UNIX-версия функции isconnected

1    int isconnected( SOCKET s, fd_set *rd, fd_set *wr, fd_set *ex )

2    {

3    int err;

4    int len = sizeoff err );

5    errno =0; /* Предполагаем, что ошибки нет. */

6    if ( !FD_ISSET( s, rd ) && !FD_ISSET( s, wr ) )

7      return 0;

8    if (getsockopt( s, SOL_SOCKET, SO_ERROR, &err, &len ) < 0)

9      return 0;

10   errno = err; /* Если мы не соединились. */

11   return err == 0;

12   }

5-7 Если сокет не доступен ни для чтения, ни для записи, значит, соединение не установлено, и возвращается нуль. Значение errno заранее установлено в нуль, чтобы вызывающая программа могла определить, что сокет действительно, не готов (разбираемый случай) или имеет Metro ошибка.

8-11 Вызываем getsockopt для получения статуса сокета. В некоторых версиях UNIX getsockopt возвращает в случае ошибки -1. В таком случае записываем в errno код ошибки. В других версиях система просто возвращает статус, оставляя его проверку пользователю. Идея кода, который корректно работает в обоих случаях, позаимствована из книги [Stevens 1998].

Согласно спецификации Winsock, ошибки, которые возвращает connect через неблокирующий сокет, индицируются путем возбуждения события исключения в select. Следует заметить, что в UNIX событие исключения всегда свидетельствует о поступлении срочных данных. Версия функции isconnected для;Windows показана в листинге 3.27.

Листинг 3.27. Windows-версия функции isconnected

1    int isconnected( SOCKET s, fd_set *rd, fd_set *wr, fd_set *ex)

2    {

3    WSASetLastError ( 0 );

4    if ( !FD_ISSET( s, rd ) && !FD_ISSET(s, wr ) )

5      return 0;

6    if ( FD_ISSET( s, ex ) )

7      return 0;

8    return 1;

9    }

3-5 Так же, как и в версии для UNIX, проверяем, соединен ли сокет. Если нет, устанавливаем последнюю ошибку в нуль и возвращаем нуль.

6-8 Если для сокета есть событие исключения, возвращается нуль, в про­тивном случае - единица.


Содержание раздела