Чтение онлайн

ЖАНРЫ

UNIX: разработка сетевых приложений
Шрифт:

Рассмотрим сначала функцию

child_make
, которая приведена в листинге 30.17. Мы создаем канал и доменный сокет Unix (см. главу 14) перед вызовом функции
fork
. После того, как создан дочерний процесс, родительский процесс закрывает один дескриптор (
sockfd[1]
), а дочерний процесс закрывает другой дескриптор (
sockfd[0]
). Более того, дочерний процесс подключает свой дескриптор канала (
sockfd[1]
) к стандартному потоку сообщений об ошибках, так что каждый дочерний процесс просто использует это устройство для
связи с родительским процессом. Этот механизм проиллюстрирован схемой, приведенной на рис. 30.3.

Листинг 30.17. Функция child_make: передача дескриптора в сервере с предварительным порождением дочерних процессов

//server/child05.c

1 #include "unp.h"

2 #include "child.h"

3 pid_t

4 child_make(int i, int listenfd, int addrlen)

5 {

6 int sockfd[2];

7 pid_t pid;

8 void child_main(int, int, int);

9 Socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);

10 if ((pid = Fork) > 0) {

11 Close(sockfd[1]);

12 cptr[i].child_pid = pid;

13 cptr[i].child_pipefd = sockfd[0];

14 cptr[i].child_status = 0;

15 return (pid); /* родительский процесс */

16 }

17 Dup2(sockfd[1], STDERR_FILENO); /* канал от дочернего процесса к

родительскому */

18 Close(sockfd[0]);

19 Close(sockfd[1]);

20 Close(listenfd); /* дочернему процессу не требуется, чтобы

он был открыт */

21 child_main(i, listenfd, addrlen); /* никогда не завершается */

22 }

Рис. 30.3. Канал после того, как дочерний и родительский процесс закрыли один конец

После создания всех дочерних процессов мы получаем схему, показанную на рис. 30.4. Мы закрываем прослушиваемый сокет в каждом дочернем процессе, поскольку только родительский процесс вызывает функцию

accept
. Мы показываем на рисунке, что родительский процесс должен обрабатывать прослушиваемый сокет, а также все доменные сокеты. Как можно догадаться, родительский процесс использует функцию
select
для мультиплексирования всех дескрипторов.

Рис. 30.4. Каналы после создания всех дочерних процессов

В листинге 30.18 показана функция

main
. В отличие от предыдущих версий этой функции, в данном случае в памяти размещаются
все наборы дескрипторов и в каждом наборе включены все биты, соответствующие прослушиваемому сокету и каналу каждого дочернего процесса. Вычисляется также максимальное значение дескриптора и выделяется память для массива структур
Child
. Основной цикл запускается при вызове функции
select
.

Листинг 30.18. Функция main, использующая передачу дескриптора

//server/serv05.c

1 #include "unp.h"

2 #include "child.h"

3 static int nchildren;

4 int

5 main(int argc, char **argv)

6 {

7 int listenfd, i, navail, maxfd, nsel, connfd, rc;

8 void sig_int(int);

9 pid_t child_make(int, int, int);

10 ssize_t n;

11 fd_set rset, masterset;

12 socklen_t addrlen, clilen;

13 struct sockaddr *cliaddr;

14 if (argc == 3)

15 listenfd = Tcp_listen(NULL, argv[1], &addrlen);

16 else if (argc == 4)

17 listenfd = Tcp_listen(argv[1], argv[2], &addrlen);

18 else

19 err_quit("usage; serv05 [ <host> ] <port#> <#children>");

20 FD_ZERO(&masterset);

21 FD_SET(listenfd, &masterset);

22 maxfd = listenfd;

23 cliaddr = Malloc(addrlen);

24 nchildren = atoi(argv[argc - 1]);

25 navail = nchildren;

26 cptr = Calloc(nchildren, sizeof(Child));

27 /* предварительное создание дочерних процессов */

28 for (i = 0; i < nchildren; i++) {

29 child_make(i, listenfd, addrlen); /* родительский процесс

завершается */

30 FD_SET(cptr[i].child_pipefd, &masterset);

31 maxfd = max(maxfd, cptr[i].child_pipefd);

32 }

33 Signal(SIGINT, sig_int);

34 for (;;) {

35 rset = masterset;

36 if (navail <= 0)

37 FD_CLR(listenfd, &rset); /* выключаем, если нет свободных

Поделиться с друзьями: