Уильям Стивенс - UNIX: разработка сетевых приложений
- Название:UNIX: разработка сетевых приложений
- Автор:
- Жанр:
- Издательство:Питер
- Год:2007
- Город:Санкт-Петербург
- ISBN:5-94723-991-4
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Уильям Стивенс - UNIX: разработка сетевых приложений краткое содержание
Новое издание книги, посвященной созданию веб-серверов, клиент-серверных приложений или любого другого сетевого программного обеспечения в операционной системе UNIX, — классическое руководство по сетевым программным интерфейсам, в частности сокетам. Оно основано на трудах Уильяма Стивенса и полностью переработано и обновлено двумя ведущими экспертами по сетевому программированию. В книгу включено описание ключевых современных стандартов, реализаций и методов, она содержит большое количество иллюстрирующих примеров и может использоваться как учебник по программированию в сетях, так и в качестве справочника для опытных программистов.
UNIX: разработка сетевых приложений - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
Листинг 20.4. Простая некорректная реализация функции pselect
//lib/pselect.c
9 #include "unp.h"
10 int
11 pselect(int nfds, fd_set *rset, fd_set *wset, fd_set *xset,
12 const struct timespec *ts, const sigset_t *sigmask)
13 {
14 int n;
15 struct timeval tv;
16 sigset_t savemask;
17 if (ts != NULL) {
18 tv.tv_sec = ts->tv_sec;
19 tv.tv_usec = ts->tv_nsec / 1000; /* наносекунды -> микросекунды */
20 }
21 sigprocmask(SIG_SETMASK, sigmask, &savemask); /* маска вызывающего
процесса */
22 n = select(nfds, rset, wset, xset., (ts == NULL) ? NULL : &tv);
23 sigprocmask(SIG_SETMASK, &savemask, NULL); /* восстанавливаем
исходную маску */
24 return (n);
25 }
Использование функций sigsetjmp и siglongjmp
Нашу проблему можно решить корректно, если отказаться от прерывания блокированного системного вызова обработчиком сигнала, вместо этого вызвав из обработчика сигнала функцию siglongjmp. Этот метод называется нелокальным оператором goto ( nonlocal goto ), поскольку мы можем использовать его для перехода из одной функции в другую. В листинге 20.5 проиллюстрирована эта технология.
Листинг 20.5. Вызов функций sigsetjmp и siglongjmp из обработчика сигнала
//bcast/dgclibcast5.c
1 #include "unp.h"
2 #include
3 static void recvfrom_alarm(int);
4 static sigjmp_buf jmpbuf;
5 void
6 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
7 {
8 int n;
9 const int on = 1;
10 char sendline[MAXLINE], recvline[MAXLINE + 1];
11 socklen_t len;
12 struct sockaddr *preply_addr;
13 preply_addr = Malloc(servlen);
14 Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
15 Signal(SIGALRM, recvfrom_alarm);
16 while (Fgets(sendline, MAXLINE, fp) != NULL) {
17 Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
18 alarm(5);
19 for (;;) {
20 if (sigsetjmp(jmpbuf, 1) != 0)
21 break;
22 len = servlen;
23 n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
24 recvline[n] = 0; /* null terminate */
25 printf("from %s: %s",
26 Sock_ntop_host(preply_addr, len), recvline);
27 }
28 }
29 free(preply_addr);
30 }
31 static void
32 recvfrom_alarm(int signo)
33 {
34 siglongjmp(jmpbuf, 1);
35 }
4 Мы выделяем буфер перехода, который будет использовать наша функция и ее обработчик сигнала.
20-23 Когда мы вызываем функцию sigsetjmpнепосредственно из нашей функции dg_cli, она устанавливает буфер перехода и возвращает нуль. Мы продолжаем работать дальше и вызываем функцию recvfrom.
31-35 Когда сигнал доставлен, мы вызываем функцию siglongjmp. Это заставляет sigsetjmpв функции dg_cliвозвратить значение, равное второму аргументу (1), который должен быть ненулевым. Это приведет к завершению цикла forв функции dg_cli.
Использование функций sigsetjmpи siglongjmpподобным образом гарантирует, что мы не останемся навсегда блокированы в вызове функции recvfromиз-за доставки сигнала в неподходящее время. Однако такое решение создает иную потенциальную проблему. Если сигнал доставляется в тот момент, когда функция printfосуществляет вывод данных, управление будет передано из printfобратно на sigsetjmp. При этом в структурах данных printfмогут возникнуть противоречия. Чтобы предотвратить эту проблему, следует объединить блокирование и разблокирование сигналов, показанное в листинге 20.2, с помощью нелокального оператора goto.
Применение IPC в обработчике сигнала функции
Существует еще один корректный путь решения нашей проблемы. Вместо того чтобы просто возвращать управление и, как мы надеемся, прерывать блокированную функцию recvfrom, наш обработчик сигнала при помощи средств IPC (Interprocess Communications — взаимодействие процессов) может сообщить функции dg_cliо том, что время таймера истекло. Это аналогично предложению, сделанному нами раньше, когда обработчик сигнала устанавливал глобальную переменную had_alarmпо истечении времени таймера. Глобальная переменная использовалась как некая разновидность IPC (поскольку она была доступна и нашей функции, и обработчику сигнала). Однако при таком решении наша функция должна была проверять эту переменную, что могло привести к проблемам синхронизации в том случае, когда сигнал доставлялся приблизительно в это же время.
Листинг 20.6 демонстрирует использование канала внутри процесса. Обработчик сигналов записывает в канал 1 байт, когда истекает время таймера, а наша функция dg_cliсчитывает этот байт, чтобы определить, когда завершить свой цикл for. Что замечательно в этом решении — проверка готовности канала осуществляется функцией select. С ее помощью мы проверяем, готов ли к считыванию сокет или канал.
Листинг 20.6. Использование канала в качестве IPC между обработчиком сигнала и нашей функцией
//bcast/dgclibcast6.c
1 #include "unp.h"
2 static void recvfrom_alarm(int);
3 static int pipefd[2];
4 void
5 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
6 {
7 int n, maxfdp1;
8 const int on = 1;
9 char sendline[MAXLINE], recvline[MAXLINE + 1];
10 fd_set rset;
11 socklen_t len;
12 struct sockaddr *preply_addr;
13 preply_addr = Malloc(servlen);
14 Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
15 Pipe(pipefd);
16 maxfdp1 = max(sockfd, pipefd[0]) + 1;
17 FD_ZERO(&rset);
18 Signal(SIGALRM, recvfrom_alarm);
19 while (Fgets(sendline, MAXLINE, fp) != NULL) {
20 Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
21 alarm(5);
22 for (;;) {
23 FD_SET(sockfd, &rset);
24 FD_SET(pipefd[0], &rset);
25 if ((n = select(maxfdp1, &rset, NULL, NULL, NULL))
26 if (errno == EINTR)
27 continue;
28 else
29 err_sys("select error");
30 }
31 if (FD_ISSET(sockfd, &rset)) {
32 len = servlen;
33 n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr,
34 &len);
35 recvline[n] = 0; /* null terminate */
36 printf("from %s: %s",
37 Sock_ntop_host(preply_addr, len), recvline);
38 }
39 if (FD_ISSET(pipefd[0], &rset)) {
40 Read(pipefd[0], &n, 1); /* истекшее время */
41 break;
42 }
43 }
44 }
45 free(preply_addr);
46 }
47 static void
48 recvfrom_alarm(int signo)
49 {
50 Write(pipefd[1], "", 1); /* в канал пишется один нулевой байт */
51 return;
52 }
15 Мы создаем обычный канал Unix. Возвращаются два дескриптора: pipefd[0]доступен для чтения, а pipefd[0] — для записи.
Мы могли бы использовать функцию socketpair и получить двусторонний канал. В некоторых системах, особенно SVR4, обычный канал Unix всегда является двусторонним, и мы можем и читать, и записывать на любом конце этого канала.
Читать дальшеИнтервал:
Закладка: