Уильям Стивенс - UNIX: взаимодействие процессов
Вернемся к программе из листинга 15.6. Запустим ее три раза подряд. Поскольку нам приходится ждать возвращения подсказки интерпретатора, чтобы запустить клиент еще раз, мы можем быть уверены, что каждый раз выполняется пятисекундная пауза:
solaris % client5 /tmp/server5 55
result: 3025
solaris % client5 /tmp/server5 66
result: 4356
solaris % client5 /tmp/server5 77
result: 5929
Взглянув на текст, выводимый сервером, мы увидим, что клиенты каждый раз обслуживались одним и тем же потоком сервера:
solaris % server5 /tmp/server5
thread id 4, arg = 55
thread id 4, arg = 66
thread id 4, arg = 77
Теперь запустим три экземпляра программы-клиента одновременно:
solaris % client5 /tmp/server5 11 & client5 /tmp/server5 22 & client5 /tmp/server5 33 &
[2] 3812
[3] 3813
[4] 3814
solaris % result: 484
result: 121
result: 1089
Выводимый сервером текст показывает, что для обработки второго и третьего вызова процедуры сервера создаются новые потоки:
thread id 4, arg = 22
thread id 5, arg = 11
thread id 6, arg = 33
Затем мы запустим еще два клиента одновременно (первые три уже завершили работу):
solaris % client5 /tmp/server5 11 & client5 /tmp/server5 22 &
[2] 3830
[3] 3831
solaris % result: 484
result: 121
При этом сервер использует созданные ранее потоки:
thread id 6, arg = 22
thread id 5, arg = 11
Этот пример показывает, что серверный процесс (то есть библиотека дверей, подключенная к нему) автоматически создает потоки серверных процедур по мере необходимости. Если приложению требуется контроль над созданием потоков, оно может его осуществить с помощью функций, описанных в разделе 15.9.
Мы также убедились, что сервер в этом случае является параллельным (concurrent): одновременно может выполняться несколько экземпляров процедуры сервера в виде отдельных потоков для обслуживания клиентов. Это следует также из того, что результат работы сервера выводится тремя экземплярами клиента одновременно пять секунд спустя после их одновременного запуска. Если бы сервер был последовательным, первый результат появился бы через 5 секунд после запуска, следующий — через 10, а последний — через 15.
Автоматическое управление потоками сервера: несколько процедур
В предыдущем примере процесс-сервер содержал лишь одну процедуру сервера. Вопрос, которым мы займемся теперь, звучит так: могут ли несколько процедур одного процесса использовать один и тот же пул потоков сервера? Чтобы узнать ответ, добавим к нашему серверу еще одну процедуру, а заодно перепишем наши программы заново, чтобы продемонстрировать более приличный стиль передачи аргументов и результатов между процессами.
Первый файл в этом примере называется squareproc.h. В нем определен один тип данных для входных аргументов функции, возводящей в квадрат, и еще один — для возвращаемых ею результатов. В этом заголовочном файле также определяется полное имя двери для данной процедуры. Его текст его приведен в листинге 15.8.
Листинг 15.8. Заголовочный файл squareproc.h//doors/squareproc.h
1 #define PATH_SQUARE_DOOR "/tmp/squareproc_door"
2 typedef struct { /* аргументы squareproc() */
3 long arg1;
4 } squareproc_in_t;
5 typedef struct { /* возврат squareproc() */
6 long res1;
7 } squareproc_out_t;
Наша новая процедура будет принимать длинное целое и возвращать квадратный корень из него (типа double). Мы определяем полное имя двери этой процедуры, структуры аргументов и результатов в заголовочном файле sqrtproc.h в листинге 15.9.
Листинг 15.9. Заголовочный файл sqrtproc.h//doors/sqrtproc.h
1 #define PATH_SQRT_DOOR "/tmp/sqrtproc_door"
2 typedef struct { /* входные данные sqrtproc() */
3 long arg1;
4 } sqrtproc_in_t;
5 typedef struct { /* возвращаемые sqrtproc() данные */
6 double res1;
7 } sqrtproc_out_t;
Программа-клиент приведена в листинге 15.10. Она последовательно вызывает две процедуры сервера и выводит возвращаемые ими результаты. Эта программа устроена аналогично другим клиентским программам, приведенным в этой главе.
Листинг 15.10. Клиент, вызывающий две процедуры//doors/client7.c
1 #include "unpipc.h"
2 #include "squareproc.h"
3 #include "sqrtproc.h"
4 int
5 main(int argc, char **argv)
6 {
7 int fdsquare, fdsqrt;
8 door_arg_t arg;
9 squareproc_in_t square_in;
10 squareproc_out_t square_out;
11 sqrtproc_in_t sqrt_in;
12 sqrtproc_out_t sqrt_out;
13 if (argc != 2)
14 err_quit("usage: client7 <integer-value>");
15 fdsquare = Open(PATH_SQUARE_DOOR, O_ROWR);
16 fdsqrt = Open(PATH_SQRT_DOOR, O_RDWR);
17 /* подготовка аргументов и вызов squareproc() */
18 square_in.arg1 = atol(argv[1]);
19 arg.data_ptr = (char*)&square_in;
20 arg.data_size = sizeof(square_in);
21 arg.desc_ptr = NULL;
22 arg.desc_num = 0;
23 arg.rbuf = (char*)&square_out;
24 arg.rsize = sizeof(square_out);
25 Door_call(fdsquare, &arg);
26 /* подготовка аргументов и вызов sqrtproc() */
27 sqrt_in.arg1 = atol(argv[1]);
28 arg.data_ptr = (char*)&sqrt_in;
29 arg.data_size = sizeof(sqrt_in);
30 arg.desc_ptr = NULL;
31 arg.desc_num = 0;
32 arg.rbuf = (char*)&sqrt_out;
33 arg.rsize = sizeof(sqrt_out);
34 Door_call(fdsqrt, &arg);
35 printf("result: %ld %gn", square_out.res1, sqrt_out.res1);
36 exit(0);
37 }
Текст двух серверных процедур приведен в листинге 15.11. Каждая из них выводит текущий идентификатор потока и значение аргумента, делает 5-секунд-ную паузу, вычисляет результат и завершает работу.
Листинг 15.11. Две процедуры сервера//doors/server7.c
1 #include "unpipc.h"
2 #include <math.h>
3 #include "squareproc.h"
4 #include "sqrtproc.h"
5 void
6 squareproc(void *cookie, char *dataptr, size_t datasize,
7 door_desc_t *descptr, size_t ndesc)
8 {
9 squareproc_in_t in;
10 squareproc_out_t out;
11 memcpy(&in, dataptr, min(sizeof(in), datasize));
12 printf("squareproc: thread id %ld, arg = %ldn",
13 pr_thread_id(NULL), in.arg1);
14 sleep(5);
15 out.res1 = in.arg1 * in.arg1;
16 Door_return((char *) &out, sizeof(out), NULL, 0);
17 }
18 void
19 sqrtproc(void *cookie, char *dataptr, size_t datasize,
20 door_desc_t *descptr, size_t ndesc)
21 {
22 sqrtproc_in_t in;
23 sqrtproc_out_t out;
24 memcpy(&in, dataptr, min(sizeof(in), datasize));
25 printf("sqrtproc: thread id %ld, arg = %ldn",
26 pr_thread_id(NULL), in.arg1);
27 sleep(5);
28 out.res1 = sqrt((double)in.arg1);
29 Door_return((char *) &out, sizeof(out), NULL, 0);
30 }
Функция main сервера, текст которой приведен в листинге 15.12, открывает дескрипторы дверей и связывает каждый из них с одной из процедур сервера.
Листинг 15.12. Функция main сервера//doors/server7.c
31 int
32 main(int argc, char **argv)
33 {
34 int fd;
35 if (argc != 1)
36 err_quit("usage: server7");
37 fd = Door_create(squareproc, NULL, 0);
38 unlink(PATH_SQUARE_DOOR);
39 Close(Open(PATH_SQUARE_DOOR, O_CREAT | O_RDWR, FILE_MODE));
40 Fattach(fd, PATH_SQUARE_DOOR);
41 fd = Door_create(sqrtproc, NULL, 0);
42 unlink(PATH_SQRT_DOOR);
43 Close(Open(PATH_SQRT_DOOR, O_CREAT | O_RDWR, FILE_MODE));
44 Fattach(fd, PATH_SQRT_DOOR);
45 for (;;)
46 pause();
47 }
Запустим программу-клиент и подождем 10 секунд до вывода результатов (как мы и ожидали):
solaris % client7 77
result: 5929 8.77496
Посмотрев на выводимый сервером текст, мы увидим, что один и тот же поток этого процесса использовался для обработки обоих запросов клиента:
solaris % server7
squareproc: thread id 4, arg = 77
sqrtproc: thread id 4, arg = 77
Это подтверждает наши предположения о том, что любой поток из пула сервера может использоваться при обработке запросов клиентов для любой процедуры.
Атрибут DOOR_UNREF для серверов
В разделе 15.3 мы отметили, что при вызове door_create для создаваемой двери можно указать атрибут DOOR_UNREF. В документации говорится, что если количество дескрипторов, относящихся к этой двери, уменьшается с двух до одного, осуществляется специальный вызов процедуры сервера. Особенность вызова заключается в том, что второй аргумент процедуры сервера (указатель на данные) при этом является константой DOOR_UNREF_DATA. Мы продемонстрируем три способа обращения к двери.