UNIX: разработка сетевых приложений
■ В Беркли-реализациях (а также POSIX) имеются два следующих правила, относящихся к функции
и неблокируемой функцииselect: во-первых, когда соединение устанавливается успешно, дескриптор становится готовым для записи [128, с. 531], и во-вторых, когда при установлении соединения встречается ошибка, дескриптор становится готовым как для чтения, так и для записи [128, с. 530].connectПРИМЕЧАНИЕЭти два правила в отношении функции select выпадают из общего ряда наших правил из раздела 6.3 относительно условий, при которых дескриптор становится готовым для чтения или записи. В сокет TCP можно записывать, если достаточно места в буфере отправки (что всегда будет выполнено в случае присоединенного сокета, поскольку мы еще ничего не записали в сокет) и сокет является присоединенным (что выполняется, только когда завершено трехэтапное рукопожатие). При наличии ошибки, ожидающей обработки, появляется возможность читать из сокета и записывать в сокет.
С неблокируемыми функциями
связано множество проблем переносимости, которые мы отметим в последующих примерах.connect16.4. Неблокируемая функция connect: клиент времени и даты
В листинге 16.7 показана наша функция
, вызывающая неблокируемую функциюconnect_nonb. Мы заменяем вызов функцииconnect, имеющийся в листинге 1.1, следующим фрагментом кода:connectif (connect_nonb(sockfd, (SA*)&servaddr, sizeof(servaddr), 0) < 0)err_sys("connect error");Первые три аргумента являются обычными аргументами функции
, а четвертый аргумент — это число секунд, в течение которых мы ждем завершения установления соединения. Нулевое значение подразумевает отсутствие тайм- аута для функцииconnect; следовательно, для установления соединения TCP ядро будет использовать свой обычный тайм-аут.selectЛистинг 16.7. Неблокируемая функция connect
//lib/connect_nonb.c1 #include "unp.h"2 int3 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 } else34 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), указывает на то, что установление соединения началось, но еще не завершилось [128, с. 466]. Любая другая ошибка возвращается вызывающему процессу.EINPROGRESSВыполнение других процессов во время установления соединенияНа этом этапе мы можем делать все, что захотим, ожидая завершения установления соединения.15Проверка немедленного завершенияЕсли неблокируемая функция16-17возвратила нуль, установление соединения завершилось. Как мы сказали, это может произойти, когда сервер находится на том же узле, что и клиент.connectВызов функции selectМы вызываем функцию18-24и ждем, когда сокет будет готов либо для чтения, либо для записи. Мы обнуляемselect, включаем бит, соответствующийrsetв этом наборе дескрипторов и затем копируемsockfdвrset. Это присваивание, возможно, является структурным присваиванием, поскольку обычно наборы дескрипторов представляются как структуры. Далее мы инициализируем структуруwsetи затем вызываем функциюtimeval. Если вызывающий процесс задает четвертый аргумент нулевым (что соответствует использованию тайм-аута по умолчанию), следует задать в качестве последнего аргумента функцииselectпустой указатель, а не структуруselectс нулевым значением (означающим, что мы не ждем вообще).timeval