read 2 bytes: 89
received EOF
Результаты оказались такими, как мы и ожидали. Каждый раз, когда отправитель посылает внеполосные данные, для получателя генерируется сигнал
SIGURG
, после чего получатель считывает один байт, содержащий внеполосные данные.
Простой пример использования функции select
Теперь мы переделаем код нашего получателя внеполосных данных и вместо сигнала
SIGURG
будем использовать функцию
select
. В листинге 24.3 показана принимающая программа.
Листинг 24.3. Принимающая программа, в которой (ошибочно) используется функция select для уведомления о получении внеполосных данных
//oob/tcprecv02.c
1 #include "unp.h"
2 int
3 main(int argc, char **argv)
4 {
5 int listenfd, connfd, n;
6 char buff[100];
7 fd_set rset, xset;
8 if (argc == 2)
9 listenfd = Tcp_listen(NULL, argv[1], NULL);
10 else if (argc ==3)
11 listenfd = Tcp_listen(argv[1], argv[2], NULL);
12 else
13 err_quit("usage: tcprecv02 [ <host> ] <port#>");
14 connfd = Accept(listenfd, NULL, NULL);
15 FD_ZERO(&rset);
16 FD_ZERO(&xset);
17 for (;;) {
18 FD_SET(connfd, &rset);
19 FD_SET(connfd, &xset);
20 Select(connfd + 1, &rset, NULL, &xset, NULL);
21 if (FD_ISSET(connfd, &xset)) {
22 n = Recv(connfd, buff, sizeof(buff) - 1, MSG_OOB);
23 buff[n] =0; /* завершающий нуль */
24 printf("read OOB byte: %s\n", n, buff);
25 }
26 if (FD_ISSET(connfd, &rset)) {
27 if ((n = Read(connfd, buff, sizeof(buff) - 1)) == 0) {
28 printf("received EOF\n");
29 exit(0);
30 }
31 buff[n] = 0; /* завершающий нуль */
32 printf("read bytes: %s\n", n, buff);
33 }
34 }
35 }
15-20
Процесс вызывает функцию
select
, которая ожидает получения либо обычных данных (набор дескрипторов для чтения,
rset
), либо внеполосных (набор дескрипторов для обработки исключений,
xset
). В обоих случаях полученные данные выводятся.
Если мы запустим эту программу, а затем — программу для отправки, которая приведена в листинге 24.1, то столкнемся со следующей ошибкой:
freebsd4 % <b>tcprecv02 9999</b>
read 3 bytes: 123
read 1 OOB byte: 4
recv error: Invalid argument
Проблема заключается в том, что функция
select
будет сообщать об исключительной ситуации, пока процесс не считает данные, находящиеся за отметкой внеполосных данных (то есть после них [128, с. 530-531]). Мы не можем считывать внеполосные данные больше одного раза, так как после первого же их считывания ядро очищает буфер, содержащий один байт внеполосных данных. Когда мы вызываем функцию
recv
, устанавливая флаг
MSG_OOB
во второй раз, она возвращает ошибку
EINVAL
.
Чтобы решить эту проблему, нужно вызывать функцию
select
для проверки на наличие исключительной ситуации только после того, как будут приняты все обычные данные. В листинге 24.4 показана модифицированная версия принимающей программы из листинга 24.3. В этой версии описанный сценарий обрабатывается корректно.
Листинг 24.4. Модификация программы, приведенной в листинге 24.3. Функция select применяется для проверки исключительной ситуации корректным образом
//oob/tcprecv03.c
1 #include "unp.h"
2 int
3 main(int argc, char **argv)
4 {
5 int listenfd, connfd, n, justreadoob = 0;
6 char buff[100];
7 fd_set rset, xset;
8 if (argc == 2)
9 listenfd = Tcp_listen(NULL, argv[1], NULL);
10 else if (argc == 3)
11 listenfd = Tcp_1isten(argv[1], argv[2], NULL);
12 else
13 err_quit("usage: tcprecv03 [ <host> ] <port#>");
14 connfd = Accept(listenfd, NULL, NULL);
15 FD_ZERO(&rset);
16 FD_ZERO(&xset);
17 for (;;) {
18 FD_SET(connfd, &rset);
19 if (justreadoob == 0)
20 FD_SET(connfd, &xset);
21 Select(connfd + 1, &rset, NULL, &xset, NULL);
22 if (FD_ISSET(connfd, &xset)) {