16.5. Тайм-аут и повторная передача
16.5. Тайм-аут и повторная передача
Рассмотрим стратегию обработки тайм-аутов и повторной передачи, используемую в средствах Sun RPC. Существуют два значения тайм-аутов:
1. Общий тайм-аут определяет время ожидания ответа сервера клиентом. Это значение используется протоколами TCP и UDP.
2. Тайм-аут повтора используется только UDP и определяет время ожидания между повторами запросов клиента, если ответ от сервера не приходит.
Для протокола TCP необходимость во введении тайм-аута повтора отсутствует, поскольку этот протокол является надежным. Если сервер Не получает запроса от клиента, время ожидания по протоколу TCP со стороны клиента закончится и клиент повторит передачу. Когда сервер получает запрос клиента, он уведомляет об этом последний. Если уведомление о получении будет утрачено по пути к клиенту, тот должен будет еще раз переслать запрос. Повторные запросы сбрасываются сервером, но уведомления об их получении отсылаются клиенту. В надежных протоколах правильность доставки (время ожидания, повторная передача, обработка лишних копий данных и лишних уведомлений) обеспечивается на транспортном уровне и не входит в задачи библиотеки RPC. Один запрос, отправленный клиентом на уровне RPC, будет получен сервером ровно в одном экземпляре на уровне RPC. В противном случае клиент RPC получит сообщение о невозможности связаться с сервером. При этом совершенно не важно, что происходит на сетевом и транспортном уровнях.
После создания дескриптора клиента можно использовать функцию clnt_control для получения информации и изменения свойств клиента. Эта функция работает аналогично fcntl для дескрипторов файлов или getsockopt и setsockopt для сокетов:
#include <rpc/rpc.h>
bool_t clnt_control(CLIENT *cl, unsigned int request, char *ptr);
/* Возвращает TRUE в случае успешного завершения, FALSE – в случае ошибки */
Здесь cl представляет собой дескриптор клиента, а на что указывает ptr — зависит от значения request.
Изменим программу-клиент из листинга 16.2, добавив в нее вызов данной функции, и выведем значения тайм-аутов. В листинге 16.9 приведен текст новой программы-клиента.
Листинг 16.9. Клиент, получающий и печатающий значения времени ожидания RPC
//sunrpc/square5/client.c
1 #include "unpipc.h"
2 #include "square.h"
3 int
4 main(int argc, char **argv)
5 {
6 CLIENT *cl;
7 square_in in;
8 square_out *outp;
9 struct timeval tv;
10 if (argc != 4)
11 err_quit("usage: client <hostname> <integer-value> <protocol>");
12 cl = Clnt_create(argv[1], SQUARE_PROG, SQUARE_VERS, argv[3]);
13 Clnt_control(cl, CLGET_TIMEOUT, (char*)&tv);
14 printf("timeout = %ld sec, %ld usec ", tv.tv_sec, tv.tv_usec);
15 if (clnt_control(cl, CLGET_RETRY_TIMEOUT, (char *) &tv) == TRUE)
16 printf("retry timeout = %ld sec, %ld usec ", tv.tv_sec, tv.tv_usec);
17 in.arg1 = atol(argv[2]);
18 if ((outp = squareproc_1(&in, cl)) == NULL)
19 err_quit("%s", clnt_sperror(cl, argv[1]));
20 printf(result: %ld ", outp->res1);
21 exit(0);
22 }
Используемый протокол является аргументом командной строки
10-12 Теперь протокол, являющийся последним аргументом clnt_create, указывается в качестве нового параметра командной строки.
Получение значения общего тайм-аута
13-14 Первым аргументом clnt_control является дескриптор клиента, вторым — тип запроса, а третьим — указатель на буфер. Наш первый запрос имеет значение CLGET_TIMEOUT; при этом возвращается значение общего тайм-аута в структуре timeval, адрес которой передается третьим аргументом. Этот запрос корректен для всех протоколов.
Попытка получения тайм-аута повтора
15-16 Следующий запрос имеет значение CLGET_RETRY_TIMEOUT. При этом должно возвращаться значение тайм-аута повтора, но этот запрос корректен только для протокола UDP. Следовательно, если функция возвращает значение FALSE, мы ничего не печатаем.
Изменим также и программу-сервер, добавив в нее ожидание продолжительностью 1000 секунд вместо 5, чтобы гарантировать получение тайм-аута по запросу клиента. Запустим сервер на узле bsdi, а клиент запустим дважды, один раз указав в качестве протокола TCP, а другой — UDP. Результат будет не таким, как мы ожидали:
solaris % date ; client bsdi 44 tcp ; date
Wed Apr 22 14:46:57 MST 1998
timeout = 30 sec, 0 usec тайм-аут 30 секунд
bsdi: RPC: Timed out
Wed Apr 22 14:47:22 MST 1998 но прошло только 25 секунд
solaris % date ; client bsdi 55 udp ; date
Wed Apr 22 14:48:05 MST 1998
timeout = –1 sec, –1 usec ерунда какая-то
retry timeout = 15 sec, 0 usec это значение кажется правильным
bsdi: RPC: Timed out
Wed Apr 22 14:48:31 MST 1998 около 25 секунд спустя
В случае с протоколом TCP значение тайм-аута, возвращенное clnt_control, было 30 секунд, но библиотека возвратила ошибку через 25 секунд. Для протокола UDP было получено значение общего тайм-аута –1.
Чтобы понять, что тут происходит, изучим текст заглушки клиента — функции squareproc_1 в файле square_clnt.c, созданном rpcgen. Эта функция вызывает библиотечную функцию с именем clnt_call, причем последним аргументом является структура типа timeval с именем TIMEOUT, объявляемая в этом файле, и инициализируется она значением 25 секунд. Этот аргумент clnt_call отменяет значение общего тайм-аута в 30 секунд для TCP и –1 для UDP. Он используется всегда, если клиент не устанавливает общий тайм-аут явно вызовом clnt_control с запросом CLSET_TIMEOUT. Если мы хотим изменить значение общего тайм-аута, следует вызывать clnt_control, а не изменять содержимое заглушки клиента.
ПРИМЕЧАНИЕ
Единственный способ проверить значение тайм-аута повтора для протокола UDP заключается в просмотре пакетов с помощью tcpdump. При этом можно увидеть, что первая дейтаграмма отправляется сразу после запуска клиента, а следующая — примерно 15 секунд спустя.
Данный текст является ознакомительным фрагментом.