UNIX: разработка сетевых приложений - Уильям Стивенс
Шрифт:
Интервал:
Закладка:
freebsd4 % tcprecv02 9999
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)) {
23 n = Recv(connfd, buff, sizeof(buff) - 1, MSG_OOB);
24 buff[n] = 0; /* завершающий нуль */
25 printf("read %d OOB byte: %sn", n, buff);
26 justreadoob = 1;
27 FD_CLR(connfd, &xset);
28 }
29 if (FD_ISSET(connfd, &rset)) {
30 if ((n = Read(connfd, buff, sizeof(buff) - 1)) == 0) {
31 printf("received EOFn");
32 exit(0);
33 }
34 buff[n] = 0; /* завершающий нуль */
35 printf("read %d bytes: %sn", n, buff);
36 justreadoob = 0;
37 }
38 }
39 }
5 Мы объявляем новую переменную с именем justreadoob, которая указывает, какие данные мы считываем — внеполосные или обычные. Этот флаг определяет, нужно ли вызывать функцию select для проверки на наличие исключительной ситуации.
26-27 Когда мы устанавливаем флаг justreadoob, мы также должны выключить бит соответствующего дескриптора в наборе для проверки исключительных ситуаций.
Теперь программа работает так, как мы ожидали.
24.3. Функция sockatmark
С приемом внеполосных данных всегда связана так называемая отметка внеполосных данных (out-of-bandmark). Это позиция в потоке обычных данных на стороне отправителя, соответствующая тому моменту; когда посылающий процесс отправляет байт, содержащий внеполосные данные. Считывая данные из сокета, принимающий процесс путем вызова функции sockatmark определяет, находится ли он в данный момент на этой отметке.
#include <sys/socket.h>
int sockatmark(int sockfd);
Возвращает: 1, если находится на отметке внеполосных данных: 0, если не на отметке; -1 в случае ошибки
ПРИМЕЧАНИЕЭта функция появилась в POSIX. Разработчики стандарта POSIX стремятся заменить отдельными функциями все вызовы ioctl с различными параметрами.
В листинге 24.5 показана реализация этой функции с помощью поддерживаемого в большинстве систем параметра SIOCATMARK функции ioctl.
Листинг 24.5. Функция sockatmark реализована с использованием функции ioctl
//lib/sockatmark.c
1 #include "unp.h"
2 int
3 sockatmark(int fd)
4 {
5 int flag;
6 if (ioctl(fd, SIOCATMARK, &flag) < 0)
7 return (-1);
8 return (flag != 0 ? 1 : 0);
9 }
Отметка внеполосных данных применима независимо от того, как принимающий процесс получает внеполосные данные: вместе с обычными данными (параметр сокета SO_OOBINLINE) или отдельно (флаг MSG_OOB). Отметка внеполосных данных часто используется для того, чтобы принимающий процесс мог интерпретировать получаемые данные специальным образом до тех пор, пока он не дойдет до этой отметки.
Пример: особенности отметки внеполосных данных
Далее мы приводим простой пример, иллюстрирующий следующие две особенности отметки внеполосных данных:
1. Отметка внеполосных данных всегда указывает на один байт дальше конечного байта обычных данных. Это означает, что, когда внеполосные данные получены вместе с обычными, функция sockatmark возвращает 1, если следующий считываемый байт был послан с флагом MSG_OOB. Если параметр SO_OOBINLINE не включен (состояние по умолчанию), то функция sockatmark возвращает 1, когда следующий байт данных является первым байтом, посланным следом за внеполосными данными.
2. Операция считывания всегда останавливается на отметке внеполосных данных [128, с. 519–520]. Это означает, что если в приемном буфере сокета 100 байт, но только 5 из них расположены перед отметкой внеполосных данных, то когда процесс выполнит функцию read, запрашивая 100 байт, возвратятся только 5 байт, расположенные до этой отметки. Эта вынужденная остановка на отметке позволяет процессу вызвать функцию sockatmark, которая определит, находится ли указатель буфера на отметке внеполосных данных.
В листинге 24.6 показана наша программа отправки. Она посылает три байта обычных данных, один байт внеполосных данных, а затем еще один байт обычных данных. Паузы между этими операциями отсутствуют.
В листинге 24.7 показана принимающая программа. В ней не используется ни функция select, ни сигнал SIGURG. Вместо этого в ней вызывается функция sokatmark, определяющая положение байта внеполосных данных.
Листинг 24.6. Программа отправки
//oob/tcpsen04.c
1 #include "unp.h"
2 int
3 main(int argc, char **argv)
4 {
5 int sockfd;
6 if (argc != 3)
7 err_quit("usage: tcpsend04 <host> <port#>");
8 sockfd = Tcp_connect(argv[1], argv[2]);
9 Write(sockfd, "123", 3);
10 printf("wrote 3 bytes of normal datan");
11 Send(sockfd, "4", 1, MSG_OOB);
12 printf("wrote 1 byte of OOB datan");
13 Write(sockfd, "5", 1);
14 printf("wrote 1 byte of normal datan");
15 exit(0);
16 }
Листинг 24.7. Принимающая программа, в которой вызывается функция sokatmark
//oob/tcprecv04.c
1 #include "unp.h"
2 int
3 main(int argc, char **argv)
4 {
5 int listenfd, connfd, n, on = 1;
6 char buff[100];
7 if (argc == 2)
8 listenfd = Tcp_listen(NULL, argv[1], NULL);
9 else if (argc == 3)
10 listenfd = Tcp_listen(argv[1], argv[2], NULL);
11 else
12 err_quit("usage- tcprecv04 [ <host> ] <port#>");
13 Setsockopt(listenfd, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on));
14 connfd = Accept(listenfd, NULL, NULL);
15 sleep(5);
16 for (;;) {
17 if (Sockatmark(connfd))
18 printf("at OOB markn");
19 if ((n = Read(connfd, buff, sizeof(buff) - 1)) == 0) {
20 printf("received EOFn");
21 exit(0);
22 }
23 buff[n] = 0; /* завершающий нуль */
24 printf("read %d bytes: %sn", n; buff);
25 }
26 }
Включение параметра сокета SO_OOBINLINE13 Мы хотим принимать внеполосные данные вместе с обычными данными, поэтому нам нужно включить параметр SO_OOBINLINE. Но если мы будем ждать, когда выполнится функция accept и установит этот параметр для присоединенного сокета, трехэтапное рукопожатие завершится и внеполосные данные могут уже прибыть. Поэтому нам нужно установить этот параметр еще для прослушиваемого сокета, помня о том, что все параметры прослушиваемого сокета наследуются присоединенным сокетом (см. раздел 7.4).
Вызов функции sleep после вызова функции accept14-15 После того как выполнена функция accept, получатель переходит в спящее состояние, что позволяет получить все данные, посланные отправителем. Это позволяет нам продемонстрировать, что функция read останавливается на отметке внеполосных данных, даже если в приемном буфере сокета имеются дополнительные данные.