Глава 3 Использование удаленных обращений к процедурам (RPC) ───────────────────────────────────────────────────────────── Введение в RPC Использование верхнего уровня Использование среднего уровня Назначение программных номеров Использование XDR для передачи произвольных типов данных Использование нижних уровней Пример серверной программы Использование XDR для выделения памяти Пример клиентской программы Использование RPC/XDR для других задач Использование выборки на сервере Использование широковещательных RPC-вызовов Пакетная обработка Установление подлинности Клиент Сервер Поддержка разных версий одной программы Проведение сериализации и десериализации разными способами Использование процедур обратного вызова Подпрограммы RPC Введение в RPC ───────────────────────────────────────────────────────────── Программы, общающиеся через сеть, нуждаются в механизме свя- зи. На нижнем уровне по поступлении пакетов подается сигнал, об- рабатываемый сетевой программой обработки сигналов. На верхнем уровне работает механизм rendezvous (рандеву), принятый в языке Ада. В NFS используется механизм вызова удаленных процедур (RPC), в котором клиент взаимодействует с сервером (см. Рисунок 3-1). В соответствии с этим процессом клиент сначала обращается к проце- дуре, посылающей запрос на сервер. По прибытии пакета с запросом сервер вызывает процедуру его вскрытия, выполняет запрашиваемую услугу, посылает ответ, и управление возвращается клиенту. ┌───────────────────────────┐ ┌───────────────────────────┐ │ │функция │ ┌ ─ ─ ─ ─┐ │ │ программа клиента │callrpc │ │ │ │ │ ──┼────────┼─> │ │ │ │ │посылает│ сервисный │ │ │пакет с │ процесс │ │ │ │ │отформа-│ (проверяет │ │ │тирован-│ пакет и │ │ │ │ │ным за- │ отправля- │ │ │просом │ ет его) запрос │ │ │ │ │ │ ────> │ │ │ │ . . выполнение│ │ │ │ │ │ . . запроса │ │ │ │ ▄▄▄▄▄▄▄▄▄▄<──── │ │ │ │ │ │ ▓перевод▓▓ ответ │ │ │ │ ответа для │ │ │ │ │ответ, │ ▓передачи▓ │ │ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄<─┼────────┼──▀▀▀▀▀▀▀▀▀▀ │ │ продолжение программы │возвра- │ │ │ │ │ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │щаемый │ └ ─ ─ ─ ─┘ │ └───────────────────────────┘функцией└───────────────────────────┘ МАШИНА A (клиент) МАШИНА B (сервер) Рисунок 3-1. Сетевое взаимодействие через механизм RPC Интерфейс RPC можно представить состоящим из трех уровней: * Верхний уровень полностью "прозрачен". Программа этого уровня может, например, содержать обращение к процедуре rnusers(), возвращающей число пользователей на удаленной машине. Вам не нужно знать об использовании механизма RPC, поскольку вы делаете обращение в программе. * Средний уровень предназначен для наиболее общих приложений. RPC-вызовами на этом уровне занимаются подпрограммы registerrpc() и callrpc(): registerrpc() получает общесис- темный код, а callrpc() исполняет вызов удаленной процеду- ры. Вызов rnusers() реализуется с помощью этих двух подп- рограмм. * Нижний уровень используется для более сложных задач, изме- няющих умолчания на значения параметров процедур. На этом уровне вы можете явно манипулировать гнездами, используемы- ми для передачи RPC-сообщений. Как правило, вам следует пользоваться верхним уровнем и избе- гать использования нижних уровней без особой необходимости. Несмотря на то, что в данном руководстве мы рассматриваем интерфейс только на Си, обращение к удаленным процедурам может быть сделано из любого языка. Работа механизма RPC для организа- ции взаимодействия между процессами на разных машинах не отлича- ется от его работы на одной машине. Использование верхнего уровня ───────────────────────────────────────────────────────────── Предположим, что вы пишите программу, в которой возникает не- обходимость узнать, сколько пользователей зарегистрировалось на удаленной машине. Это можно сделать с помощью библиотечной подп- рограммы rnusers(): ┌────────────────────────────────────────────────────────────┐ │ │ │ #include │ │ │ │ main(argc,argv) │ │ int argc; │ │ char **argv; │ │ { │ │ unsigned num; │ │ │ │ if (argc < 2) { │ │ fprintf(stderr, "usage: rnusers hostname\n");│ │ exit(1); │ │ } │ │ if ((num = rnusers(argv[1])) < 0) { │ │ fprintf(stderr, "error: rnusers\n"); │ │ exit(-1); │ │ } │ │ printf("%d users on %s\n", num, argv[1]); │ │ exit(0); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограммы библиотеки RPC, в том числе rnusers(), включены в Си-библиотеку librpcsvc.a. Таким образом, компиляция вышеописанной программы может быть запущена командой: $ cc program.c -lrpcsvc -lrpc -lsocket Еще одна библиотечная подпрограмма - rstat() - получает ста- тистику о работе удаленной системы. Использование среднего уровня ───────────────────────────────────────────────────────────── Простейший интерфейс, явно выполняющий RPC-вызовы, пользуется функциями callrpc() и registerrpc(). Еще один способ получения числа удаленных пользователей: ┌────────────────────────────────────────────────────────────┐ │ │ │ #include │ │ #include │ │ │ │ main(argc,argv) │ │ int argc; │ │ char **argv; │ │ { │ │ unsigned long nusers; │ │ │ │ if (argc < 2) { │ │ fprintf(stderr, "usage: rnusers hostname\n"); │ │ exit(-1); │ │ } │ │ if (callrpc(argv[1],RUSERSPROG,RUSERSVERS,RUSERSPROC_NUM,│ │ xdr_void, 0, xdr_u_long, &nusers) != 0) { │ │ fprintf(stderr, "error: callrpc\n"); │ │ exit(1); │ │ } │ │ printf("number of users on %s is %d\n", argv[1], nusers);│ │ exit(0); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Номера программы, версии и процедуры описывают каждую RPC-процедуру. Номер программы определяет группу связанных с ней удаленных процедур, каждая из которых имеет свой номер. Каждая программа, кроме того, имеет номер версии, поэтому при любом из- менении, касающемся удаленных процедур (добавление новой процеду- ры, например), в присвоении программе нового номера необходимости нет. Если вы хотите обратиться к процедуре для того, чтобы найти число удаленных пользователей, соответствующие номера программы, версии и процедуры ищутся в справочнике подобно тому, как имя программы распределения памяти ищется в момент выделения ресурсов памяти. Простейшей подпрограммой в библиотеке RPC, используемой для вызова удаленной процедуры, является callrpc(). Она имеет восемь параметров. * Первым параметром выступает имя удаленной машины. * Следующими тремя параметрами являются номера программы, версии и процедуры. * Следующие два параметра описывают аргумент RPC-вызова. * Последние два параметра используются для значения, возвра- щаемого вызовом. Если подпрограмма callrpc() выполняется успешно, она возвра- щает ноль. Точное значение остальных кодов возврата находится в и фактически предполагает преобразование enum clnt_stat к целому типу. Поскольку на разных машинах типы данных могут иметь различное представление, подпрограмме callrpc() требуется как тип аргумента RPC, так и указатель на сам аргумент, а также аналогичная инфор- мация по результату. Для RUSERSPROC_NUM возвращаемое значение имеет тип unsigned long. Это означает, что первым параметром, возвращаемым подпрог- раммой callrpc(), является xdr_u_long (сообщающий о том, что ре- зультат имеет тип unsigned long), а вторым - &nusers, указатель на место размещения результата. Поскольку у RUSERSPROC_NUM нет аргумента, параметр callrpc(), соответствующий типу аргумента, имеет значение xdr_void, а указатель на аргумент - NULL. Процедура callrpc() использует пользовательский дейтаграммный протокол (UDP) для посылки сообщения по сети и ожидания ответа. Если UDP не получает ответа, он вновь посылает сообщение и ждет ответа. Несколько раз повторив эту процедуру и ничего не добив- шись, callrpc() возвращает управление с передачей кода ошибки. Методы установки числа попыток или смены протокола требуют ис- пользования подпрограмм нижнего уровня библиотеки RPC. Пример удаленной серверной процедуры, корреспондирующей с callrpc(): ┌────────────────────────────────────────────────────────────┐ │ │ │ char * │ │ nuser(indata) │ │ char *indata; │ │ { │ │ static int nusers; │ │ │ │ /* │ │ * текст вычисления числа пользователей │ │ * и размещения результата в переменной nusers │ │ */ │ │ return ((char *)&nusers); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Процедура имеет один аргумент, который является указателем на вход удаленной процедуры и в котором возвращается указатель на результат. В текущей версии Си символьные указатели имеют обоб- щенный смысл, поэтому и входной аргумент, и возвращаемое значение приводятся к типу char *. Обычно сервер регистрирует все RPC-вызовы, планируемые на об- работку, и переходит в состояние ожидания обслуживания запросов. В нашем примере регистрируется только одна процедура, поэтому те- ло серверной программы будет выглядеть следующим образом: ┌────────────────────────────────────────────────────────────┐ │ │ │ #include │ │ #include │ │ │ │ char *nuser(); │ │ │ │ main() │ │ { │ │ registerrpc(RUSERSPROG,RUSERSVERS,RUSERSPROC_NUM,nuser, │ │ xdr_void, 0, xdr_u_long); │ │ svc_run(); /* не возвращает управление */ │ │ fprintf(stderr, "Error: svc_run returned!\n"); │ │ exit(1); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма registerrpc() устанавливает соответствие между Си-процедурой и номером RPC-процедуры. Параметры подпрограммы: * Первыми тремя параметрами, RUSERSPROG, RUSERSVERS и RUSERSPROC_NUM, являются номера программы, версии и проце- дуры, соответствующие регистрируемой удаленной процедуре. * Имя Си-процедуры - nuser. * Типы входных и выходных данных - xdr_void и xdr_u_long. Подпрограмма registerrpc() может использоваться только транс- портным механизмом UDP; таким образом, она работает в сочетании с обращениями, генерируемыми с помощью callrpc(). ----------------------------------------------------------------- Замечание. Транспортный механизм UDP имеет дело с аргументами и резуль- татами, имеющими не более 8 Кбайт в длину. ----------------------------------------------------------------- Назначение программных номеров Программные номера назначаются группами по 0x20000000 (536870912), в соответствии со следующей схемой: 0 - 1fffffff определяется фирмой Sun Microsystems 20000000 - 3fffffff определяется пользователем 40000000 - 5fffffff нерезидентный 60000000 - 7fffffff зарезервирован 80000000 - 9fffffff зарезервирован a0000000 - bfffffff зарезервирован c0000000 - dfffffff зарезервирован e0000000 - ffffffff зарезервирован * Первая группа номеров используется фирмой Sun Microsystems. Они идентичны во всех системах и приложениях. Если пользо- ватель разрабатывает программу, представляющую широкий ин- терес, ей следует присвоить номер из первого диапазона. * Вторая группа номеров зарезервирована для специальных нужд заказчика. В первую очередь она используется при отладке новых программ. * Третья группа зарезервирована для приложений, генерирующих программные номера динамически. * Остальные группы зарезервированы для будущего использова- ния. Использование XDR для передачи произвольных типов данных В предыдущем примере RPC-вызова передается один аргумент типа unsigned long (длинное без знака). RPC позволяет обрабатывать произвольные структуры данных (независимо от разрядности машины и формата памяти), всегда приводя их к сетевому стандарту XDR (внешнее представление данных) перед посылкой в канал. Процесс преобразования данных из машинного представления в формат XDR на- зывается сериализацией, а обратный процесс - десериализацией. Тип в подпрограммах callrpc() и registerrpc() может быть описан встроенной процедурой (xdr_u_long() в предыдущем примере) или пользовательской процедурой. Встроенные процедуры описания типа в XDR: xdr_int() xdr_u_int() xdr_enum() xdr_long() xdr_u_long() xdr_bool() xdr_short() xdr_u_short() xdr_string() В качестве примера пользовательской процедуры рассмотрим программу посылки следующей структуры: ┌────────────────────────────────────────────────────────────┐ │ │ │ struct simple { │ │ int a; │ │ short b; │ │ } simple; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмму callrpc следует вызвать строкой: callrpc(hostname,PROGNUM,VERSNUM,PROCNUM,xdr_simple,&simple...); где xdr_simple() записывается следующим образом: ┌────────────────────────────────────────────────────────────┐ │ │ │ #include │ │ │ │ xdr_simple(xdrsp, simplep) │ │ XDR *xdrsp; │ │ struct simple *simplep; │ │ { │ │ if (!xdr_int(xdrsp, &simplep->a)) │ │ return (0); │ │ if (!xdr_short(xdrsp, &simplep->b)) │ │ return (0); │ │ return (1); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма XDR возвращает ненулевое значение (TRUE в Си) в случае успешного завершения и нулевое - в противном случае. Пол- ное описание XDR содержится в главе 4, поэтому здесь мы только приведем несколько примеров реализации механизма XDR. Помимо встроенных процедур, в XDR имеется также несколько конструкционных блоков: xdr_array() xdr_bytes() xdr_reference() xdr_union() Для того, чтобы послать массив переменных целого типа, вы мо- жете упаковать его в структуру, подобную следующей: ┌────────────────────────────────────────────────────────────┐ │ │ │ struct varintarr { │ │ int *data; │ │ int arrlnth; │ │ } arr; │ │ │ └────────────────────────────────────────────────────────────┘ и сделать RPC-вызов: callrpc(hostname,PROGNUM,VERSNUM,PROCNUM,xdr_varintarr,&arr...); со следующим определением xdr_varintarr(): ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_varintarr(xdrsp, varintarr) │ │ XDR *xdrsp; │ │ struct varintarr *arrp; │ │ { │ │ return (xdr_array(xdrsp,&arrp->data,&arrp->arrlnth,│ │ MAXLEN, sizeof(int), xdr_int)); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Эта подпрограмма использует в качестве своих параметров выход на XDR, указатель на массив, указатель на размер массива, макси- мально-допустимый размер массива, размер каждого элемента массива и XDR-процедуру для обработки каждого элемента массива. Если размер массива известен заранее, для посылки массива длиной SIZE можно воспользоваться следующим: ┌────────────────────────────────────────────────────────────┐ │ │ │ int intarr[SIZE]; │ │ │ │ xdr_intarr(xdrsp, intarr) │ │ XDR *xdrsp; │ │ int intarr[]; │ │ { │ │ int i; │ │ │ │ for (i = 0; i < SIZE; i++) { │ │ if (!xdr_int(xdrsp, &intarr[i])) │ │ return (0); │ │ } │ │ return (1); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ XDR при десериализации всегда преобразует количественные зна- чения в 4-байтовые множества. Таким образом, если в любом из вы- шеописанных примеров вместо целых используются символьные значе- ния, каждый символ будет занимать 32 бита. На этом основано действие XDR-процедуры xdr_bytes(), которая похожа на xdr_array(), но упаковывает символы. Она имеет четыре параметра, совпадающие с первыми четырьмя параметрами xdr_array(). Для строк, дополненных пустыми символами, существует также подпрог- рамма xdr_string(), которая представляет собой xdr_bytes() без параметра длины. При сериализации она получает длину строки от strlen(); при десериализации создает строку, дополненную пустыми символами. Последний пример иллюстрирует вызов функций xdr_simple(), xdr _string() и xdr_reference(): ┌────────────────────────────────────────────────────────────┐ │ │ │ struct finalexample { │ │ char *string; │ │ struct simple *simplep; │ │ } finalexample; │ │ │ └────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_finalexample(xdrsp, finalp) │ │ XDR *xdrsp; │ │ struct finalexample *finalp; │ │ { │ │ int i; │ │ │ │ if (!xdr_string(xdrsp, &finalp->string, MAXSTRLEN))│ │ return (0); │ │ if (!xdr_reference(xdrsp, &finalp->simplep, │ │ sizeof(struct simple), xdr_simple)) │ │ return (0); │ │ return (1); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Использование нижних уровней ───────────────────────────────────────────────────────────── До сих пор мы рассматривали примеры, в которых RPC выполняет многие вещи автоматически. В этом разделе будет показано, как из- менить умолчания на значения рабочих параметров с помощью нижних уровней библиотеки RPC. В общем случае вам нет необходимости обращаться к этим уров- ням. Однако этого не удастся избежать в следующих ситуациях: * Посылка более 8 Кбайт информации. * Выделение и освобождение памяти в процессе сериализации и десериализации с помощью XDR-процедур. См. раздел "Исполь- зование XDR для выделения памяти". * Идентификация клиента или сервера путем вручения "веритель- ных грамот" и их проверки. См. раздел "Установление подлин- ности". Пример серверной программы В registerrpc() сделаны следующие допущения: * Используется дейтаграммный протокол UDP. * В процессе десериализации пользователь не желает предприни- мать что-либо необычное, поскольку этот процесс выполняется автоматически перед вызовом пользовательской серверной про- цедуры. Сервер для программы nusers, приведенный ниже, написан с ис- пользованием нижнего уровня пакета RPC, не учитывающего указанные допущения. ┌────────────────────────────────────────────────────────────┐ │ │ │ #include │ │ #include │ │ #include │ │ │ │ int nuser(); │ │ │ │ main() │ │ { │ │ SVCXPRT *transp; │ │ │ │ transp = svcudp_create(RPC_ANYSOCK); │ │ if (transp = NULL) { │ │ fprintf(stderr,"could not create an RPC server\n");│ │ exit(1); │ │ } │ │ pmap_unset(RUSERSPROG, RUSERSVERS); │ │ if (!svc_register(transp,RUSERSPROG,RUSERSVERS,nuser, │ │ IPPROTO_UDP)) { │ │ fprintf(stderr,"could not register RUSER service\n");│ │ exit(1); │ │ } │ │ svc_run(); │ │ fprintf(stderr, "should never reach this point\n"); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────┐ │ │ │ nuser(rqstp, transp) │ │ struct svc_req *rqstp; │ │ SVCXPRT *transp; │ │ { │ │ unsigned long nusers; │ │ │ │ switch (rqstp->rq_proc) { │ │ case NULLPROC; │ │ if (!svc_sendreply(transp, xdr_void, 0)) { │ │ fprintf(stderr, "could not reply to RPC call\n");│ │ exit(1); │ │ } │ │ return; │ │ case RUSERSPROC_NUM; │ │ /* │ │ * следует текст вычисления числа пользователей │ │ * и помещения результата в переменную nusers │ │ */ │ │ if (!svc_sendreply(transp, xdr_u_long, &nusers) { │ │ fprintf(stderr, "could not reply to RPC call\n");│ │ exit(1); │ │ } │ │ return; │ │ default: │ │ svcerr_noproc(transp); │ │ return; │ │ } │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Сначала сервер получает транспортный выход, используемый для посылки RPC-сообщений. Процедура registerrpc() использует svcudp_ create() для получения выхода на UDP. Если вам нужен надежный протокол, вызовите вместо этого svctcp_create(). Если аргумент функции svcudp_create() имеет значение RPC_ANYSOCK, библиотека RPC создает гнездо для приема RPC-вызовов. В противном случае, значение аргумента интерпретируется как номер существующего гнез- да. Если гнездо привязано пользователем к порту, номера портов в svcudp_create() и clntudp_create() должны совпадать. Если пользователь указал RPC_ANYSOCK или несвязанное гнездо, система вычисляет номера портов следующим образом: при запуске сервера процесс отображения портов на локальной машине выбирает номер порта для RPC-процедуры, если гнездо, указанное в svcudp_create(), еще не привязано. Когда в вызове clntudp_create() участвует несвязанное гнездо, система запрашива- ет номер порта у процесса отображения портов на той машине, кото- рой адресован вызов. Если указанный процесс не активен или не имеет порта для RPC-вызова, вызов заканчивается неудачей. Пользо- ватели могут обращаться к процессу отображения портов сами. Соот- ветствующие номера процедур находятся в файле . Следующим шагом после создания SVCXPRT является вызов pmap_unset(), так что если сервер nusers прекратил работу ранее, перед перезапуском от него не осталось и следа. Точнее говоря, pmap_unset() убирает запись о RUSERS из таблиц процесса отображе- ния портов. Номер программы для nusers связан с процедурой nuser(). Пос- ледний аргумент svc_register() обычно описывает используемый про- токол, в нашем случае - IPPROTO_UDP. Следует заметить, что за ис- ключением registerrpc(), в процессе регистрации не участвуют ни- какие XDR-процедуры. Кроме того, регистрация ведется на программ- ном уровне, а не на процедурном. Пользовательская процедура nuser() должна вызывать и обслужи- вать соответствующие XDR-процедуры по их номерам. Процедура nuser () выполняет два действия, которые автоматически выполняет и про- цедура registerrpc(): * Процедура NULLPROC возвращает управление без передачи аргу- ментов. На этом может строиться установление факта удален- ного выполнения программы. * Проверка номеров процедур. При обнаружении неверного номера ошибка обрабатывается подпрограммой svcerr_noproc(). Пользовательская процедура преобразует результаты и возвраща- ет их процессу, вызвавшему RPC, через svc_sendreply(). Ее первый параметр имеет значение SVCXPRT, второй - XDR-процедура, а третий - указатель на возвращаемую информацию. Мы не рассматривали еще, каким образом сервер обрабатывает RPC-программу передачи данных. В качестве примера можно привести процедуру с именем RUSERSPROC_BOOL, имеющую аргумент nusers и возвращающую TRUE или FALSE в зависимости от того, прошли ли nusers регистрацию или нет: ┌────────────────────────────────────────────────────────────┐ │ │ │ case RUSERSPROC_BOOL: { │ │ int bool; │ │ unsigned nuserquery; │ │ │ │ if (!svc_getargs(transp, xdr_u_int, &nuserquery)) { │ │ svcerr_decode(transp); │ │ return; │ │ } │ │ /* │ │ * текст, устанавливающий nusers = числу пользователей │ │ */ │ │ if (nuserquery = nusers) │ │ bool = TRUE; │ │ else │ │ bool = FALSE; │ │ if (!svc_sendreply(transp, xdr_bool, &bool)) { │ │ fprintf(stderr, "could not reply to RPC call\n");│ │ exit(1); │ │ } │ │ return; │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Процедура svc_getargs() принимает в качестве аргументов выход SVCXPRT, XDR-подпрограмму и указатель на место размещения входных данных. Использование XDR для выделения памяти Помимо ввода и вывода данных XDR-процедуры занимаются выделе- нием памяти. Поэтому вторым параметром процедуры xdr_array() выс- тупает указатель на массив, а не сам массив. Если он имеет значе- ние NULL, xdr_array() выделяет место для массива и возвращает указатель на него, помещая размер массива в третий аргумент. В качестве примера рассмотрим следующую XDR-процедуру xdr_chararr1(), имеющую дело с байтовым массивом фиксированной размерности SIZE: ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_chararr1(xdrsp, chararr) │ │ XDR *xdrsp; │ │ char chararr[]; │ │ { │ │ char *p; │ │ int len; │ │ │ │ p = chararr; │ │ len = SIZE; │ │ return (xdr_bytes(xdrsp, &p, &len, SIZE)); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Вы можете вызвать ее с сервера: ┌────────────────────────────────────────────────────────────┐ │ │ │ char chararr[SIZE]; │ │ │ │ svc_getargs(transp, xdr_chararr1, chararr); │ │ │ └────────────────────────────────────────────────────────────┘ где chararr уже имеет выделенное пространство. Если вы хотите, чтобы механизм XDR выделил память, эту проце- дуру следует переписать следующим образом: ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_chararr2(xdrsp, chararrp) │ │ XDR *xdrsp; │ │ char **chararrp; │ │ { │ │ int len; │ │ │ │ len = SIZE; │ │ return (xdr_bytes(xdrsp, chararrp, &len, SIZE)); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ RPC-вызов после этого может иметь следующий вид: ┌────────────────────────────────────────────────────────────┐ │ │ │ char *arrptr; │ │ │ │ arrptr = NULL; │ │ svc_getargs(transp, xdr_chararr2, &arrptr); │ │ /* │ │ * используйте результат здесь │ │ */ │ │ svc_freeargs(xdrsp, xdr_chararr2, &arrptr); │ │ │ └────────────────────────────────────────────────────────────┘ После использования символьный массив может быть очищен функ- цией svc_freeargs(). Если в подпрограмме xdr_finalexample() при выполнении процедуры svc_getargs(transp, xdr_finalexample, &finalp); результат finalp->string имеет значение NULL, то процедура svc_freeargs(xdrsp, xdr_finalexample, &finalp); очищает массив, отведенный для хранения finalp->string; в против- ном случае ресурсы не освобождаются. Аналогичное истинно и в от- ношении finalp->simplep. Итак, каждая XDR-процедура отвечает за сериализацию, десериа- лизацию и выделение памяти. Если XDR-процедура вызывается из callrpc(), работает функция сериализации. При вызове из svc_getargs() используется десериализатор. При вызове из svc_freeargs() выделяется память. Пример клиентской программы Если вы используете callrpc, вам недоступен контроль над ме- ханизмом RPC или гнездом, используемым для транспортировки дан- ных. Чтобы проиллюстрировать работу уровня, позволяющего настраи- вать эти механизмы, рассмотрим следующую программу, использующую вызов функции nusers: ┌────────────────────────────────────────────────────────────┐ │ │ │ #include │ │ #include │ │ #include │ │ #include │ │ #include │ │ #include │ │ │ │ main(argc, argv) │ │ int argc; │ │ char **argv; │ │ { │ │ struct hostent *hp; │ │ struct timeval pertry_timeout, total_timeout; │ │ struct sockaddr_in server_addr; │ │ int addrlen, sock = RPC_ANYSOCK; │ │ register CLIENT *client; │ │ enum clnt_stat clnt_stat; │ │ unsigned long nusers; │ │ │ │ if (argc < 2) { │ │ fprintf(stderr, "usage: nusers hostname\n"); │ │ exit(-1); │ │ } │ │ if ((hp = gethostbyname(argv[1])) = NULL) { │ │ fprintf(stderr,"cannot get addr for '%s'\n", │ │ argv[1]); │ │ exit(-1); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────┐ │ │ │ pertry_timeout.tv_sec = 3; │ │ pertry_timeout.tv_usec = 0; │ │ addrlen = sizeof(struct sockaddr_in); │ │ bcopy(hp->h_addr,(caddr_t)&server_addr.sin_addr, │ │ hp->h_length); │ │ server_addr.sin_family = AF_INET; │ │ server_addr.sin_port = 0; │ │ if ((client = clntudp_create(&server_addr, RUSERSPROG, │ │ RUSERSVERS, pertry_timeout, &sock)) = NULL) { │ │ perror("clntudp_create"); │ │ exit(-1); │ │ } │ │ total_timeout.tv_sec = 20; │ │ total_timeout.tv_usec = 0; │ │ clnt_stat = clnt_call(client,RUSERSPROC_NUM,xdr_void,0, │ │ xdr_u_long,&nusers,total_timeout); │ │ if (clnt_stat != RPC_SUCCESS) { │ │ clnt_perror(client, "rpc"); │ │ exit(-1); │ │ } │ │ clnt_destroy(client); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Версия callrpc() для нижнего уровня именуется clnt_call(), в ней вместо имени host'а используется указатель CLIENT. Параметры функции clnt_call(): * указатель CLIENT * номер процедуры * XDR-процедура для сериализации аргумента * указатель на аргумент * XDR-процедура для десериализации возвращаемого значения * указатель на место размещения возвращаемого значения * интервал ожидания ответа в секундах. Указатель CLIENT кодируется с помощью транспортного механиз- ма. Процедура callrpc() использует механизм UDP; поэтому она об- ращается к clntudp_create() для получения указателя CLIENT. Для работы с механизмом TCP используйте clnttcp_create(). Параметры функции clntudp_create(): * адрес сервера * длина адреса сервера * номер программы * номер версии * длительность таймаута (между попытками) * указатель на гнездо Последним аргументом функции clnt_call() является общая про- должительность ожидания ответа. Таким образом, количество попыток вычисляется делением таймаута clnt_call() на таймаут clntudp_create(). Следует заметить, что clnt_destroy() освобождает любое прост- ранство, связанное с указателем CLIENT, но не закрывает ассоции- рованное с ним гнездо, которое было передано в качестве аргумента функции clntudp_create(). Причина этого заключается в том, что если одно и то же гнездо используется несколькими клиентскими вы- ходами, то можно закрыть один выход, не разрушая гнезда, исполь- зуемого другими выходами. Для того, чтобы создать потоковую связь, замените вызов clntudp_create() на вызов clnttcp_create(). clnttcp_create(&server_addr, prognum, versnum, &socket, inputsize, outputsize); Аргумент "таймаут" здесь отсутствует; вместо этого необходимо указать размеры буферов получения и посылки. При выполнении clnttcp_create() создается соединение TCP. Все RPC-вызовы, ис- пользующие указатель CLIENT, будут работать с этим соединением. На серверном конце RPC-вызова, использующего TCP, svcudp_create() заменяется на svctcp_create(). Использование RPC/XDR для других задач ───────────────────────────────────────────────────────────── В настоящем разделе рассматриваются некоторые другие аспекты RPC. Использование выборки на сервере Рассмотрим процесс, который в ходе выполнения некоторой дея- тельности обрабатывает поступающие к RPC запросы. Если эта дея- тельность связана с периодической корректировкой структуры дан- ных, процесс может выставить сигнал тревоги перед вызовом svc_run (). Однако, если эта деятельность включает ожидание файлового дескриптора, функция svc_run() не сработает. Текст svc_run(): ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ svc_run() │ │ { │ │ int readfds; │ │ │ │ for (;;) { │ │ readfds = svc_fds; │ │ switch (select(32,&readfds,NULL,NULL,NULL)) { │ │ │ │ case -1: │ │ if (errno == EINTR) │ │ continue; │ │ perror("rstat: select"); │ │ return; │ │ case 0: │ │ break; │ │ default: │ │ svc_getreq(readfds); │ │ } │ │ } │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Вы можете обойти функцию svc_run() и вызвать svc_getreq() не- посредственно. Для этого вам нужно знать файловые дескрипторы гнезд, связанных с программами, от которых вы ожидаете сигнала. Таким образом, вы можете описать свои операторы выборки, ожидаю- щие получения как гнезда RPC, так и ваших собственных дескрипто- ров. Использование широковещательных RPC-вызовов Протоколы pmap и RPC реализуют широковещательные RPC-вызовы. Между обычными и широковещательными вызовами имеются следующие основные различия: * Обычные RPC-вызовы ожидают один ответ, в то время как широ- ковещательные - несколько (от каждой реагирующей машины). * Широковещательные RPC-вызовы поддерживаются только пакетно- ориентированными транспортными протоколами (подобными UDP/IP). * Широковещательные RPC-вызовы трактуют все "неуспешные" от- веты как мусор, отбрасывая их. Таким образом, если номера версий у передатчика и удаленной функции не совпадают, пользователь широковещательного RPC-вызова об этом никогда не узнает. * Все широковещательные сообщения поступают на порт программы отображения портов. Другими словами, через механизм широко- вещательного RPC доступны только услуги, регистрирующие се- бя с помощью программы отображения портов. ┌────────────────────────────────────────────────────────────┐ │ │ │ #include │ │ │ │ enum clnt_stat clnt_stat; │ │ │ │ clnt_stat = │ │ clnt_broadcast(prog, vers, proc, xargs, argsp, │ │ xresults, resultsp, eachresult) │ │ ulong prog; /* номер программы */ │ │ ulong vers; /* номер версии */ │ │ ulong proc; /* номер процедуры */ │ │ xdrproc_t xargs; /* xdr-процедура для аргументов */ │ │ caddr_t argsp; /* указатель на аргументы */ │ │ xdrproc_t xresults; /* xdr-процедура для результатов */│ │ caddr_t resultsp; /* указатель на результаты */ │ │ bool_t (*eachresult) (); /* вызов с каждым полученным ре- │ │ * зультатом */ │ │ │ └────────────────────────────────────────────────────────────┘ Процедура eachresult() вызывается при каждом получении ре- зультата с допустимым значением. Возвращаемое ею значение являет- ся логическим и показывает, нужны ли клиенту еще ответы или нет. ┌────────────────────────────────────────────────────────────┐ │ │ │ bool_t done; │ │ │ │ done = eachresult(resultsp, raddr) │ │ caddr_t resultsp; │ │ struct sockaddr_in *raddr; /* адрес машины, посылающей │ │ * ответ */ │ │ │ └────────────────────────────────────────────────────────────┘ Если done имеет значение TRUE (Истина), широкое вещание прек- ращается и clnt_broadcast() завершается успешно. В противном слу- чае процедура ожидает поступления иного ответа. По истечении нес- кольких секунд ожидания запрос повторяется. Если ответов нет, процедура возвращает значение RPC_TIMEDOUT. Для того, чтобы рас- шифровать ошибки clnt_stat, передайте код ошибки функции clnt_perrno(). Пакетная обработка Архитектура RPC предусматривает посылку клиентами вызывающего сообщения и ожидание ответа серверов об успешном выполнении опе- рации. При этом клиенты не выполняют никаких вычислений, пока серверы занимаются обработкой запроса. Это снижает эффективность работы, ибо какому же клиенту захочется подтверждать каждое пос- ланное сообщение. Продолжать вычисления в процессе ожидания отве- та позволяют средства пакетной обработки RPC-запросов. Сообщения могут быть включены в конвейер обращений к конкрет- ному серверу; это и называется пакетной обработкой, которая предполагает следующее: * Каждый RPC-вызов (запрос) в конвейере не требует от сервера ответа, и сервер не посылает ответного сообщения. * Конвейер запросов опирается на надежный транспортный прото- кол, подобный TCP/IP. Поскольку сервер не отвечает уже на каждый запрос, клиент мо- жет генерировать новые запросы, обрабатываемые сервером парал- лельно с ранее посланными. Протокол TCP/IP, кроме того, может вы- полнять буферизацию нескольких запросов и посылать их серверу одной системной функцией write. Параллельная обработка существен- но снижает накладные расходы, связанные с взаимодействием процес- сов, протекающих у сервера и клиента, а также общее время обра- ботки серии запросов. Буферизация запросов делает законными действия клиента по останову конвейера в подходящий для этого момент. Рассмотрим пример. Допустим, что функция преобразования стро- ки имеет две похожие формы: одна после преобразования строки возвращает "пусто", другая ничего не возвращает. Текст функции выглядит следующим образом: ┌────────────────────────────────────────────────────────────┐ │ │ │ #include │ │ #include │ │ #include │ │ │ │ void windowdispatch(); │ │ │ │ main() │ │ { │ │ SVCPRT *transp; │ │ │ │ transp = svctcp_create(RPC_ANYSOCK, 0, 0); │ │ if (transp == NULL) { │ │ fprintf(stderr, "could not create an RPC server\n");│ │ exit(1); │ │ } │ │ pmap_unset(WINDOWPROG, WINDOWVERS); │ │ if (!svc_register(transp, WINDOWPROG, WINDOWVERS, │ │ windowdispatch, IPPROTO_TCP)) { │ │ fprintf(stderr, "could not register WINDOW service\n");│ │ exit(1); │ │ } │ │ svc_run(); │ │ fprintf(stderr, "should never reach this point\n"); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ windowdispatch(rqstp, transp) │ │ struct svc_req *rqstp; │ │ SVCXPRT *transp; │ │ { │ │ char *s = NULL; │ │ │ │ switch (rqstp->rq_proc) { │ │ case NULLPROC: │ │ if (!svc_sendreply(transp, xdr_void, 0)) { │ │ fprintf(stderr, "could not reply to RPC call\n"); │ │ exit(1); │ │ } │ │ return; │ │ case RENDERSTRING: │ │ if (!svc_getargs(transp, xdr_wrapstring, &s)) { │ │ fprintf(stderr, "could not decode arguments\n"); │ │ svcerr_decode(transp); /* tell caller of mistake*/│ │ break; │ │ } │ │ /* │ │ * вставить запрос на обработку строки s │ │ */ │ │ if (!svc_sendreply(transp, xdr_void, NULL)) { │ │ fprintf(stderr, "could not reply to RPC call\n"); │ │ exit(1); │ │ } │ │ break; │ │ │ └────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────┐ │ │ │ case RENDERSTRING_BATCHED: │ │ if (!svc_getargs(transp, xdr_wrapstring, &s)) { │ │ fprintf(stderr, "could not decode arguments\n"); │ │ /* │ │ * на ошибки протокола не реагируем │ │ */ │ │ break; │ │ } │ │ /* │ │ * вставить запрос на обработку строки s, │ │ * но ответа не посылать ! │ │ */ │ │ break; │ │ default: │ │ svcerr_noproc(transp); │ │ return; │ │ } │ │ /* │ │ * освобождаем строку, выделенную под декодирование │ │ * аргументов │ │ */ │ │ svc_freeargs(transp, xdr_wrapstring, &s); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Конечно же, функция может в одной процедуре и обрабатывать строку и проверять по значению логической переменной, нужен ответ или нет. Чтобы воспользоваться преимуществами пакетной обработки, кли- енту следует выполнять RPC-запросы на основе транспортного прото- кола TCP. Запросы должны иметь следующие атрибуты: * XDR-процедура результата должна иметь значение NULL. * Таймаут RPC-вызова должен быть нулевым. Рассмотрим пример клиентского процесса, который в пакетном режиме обрабатывает набор строк; обработка прекращается при полу- чении клиентом пустой строки: ┌────────────────────────────────────────────────────────────┐ │ │ │ #include │ │ #include │ │ #include │ │ #include │ │ #include │ │ #include │ │ │ │ main(argc, argv) │ │ int argc; │ │ char **argv; │ │ { │ │ struct hostent *hp; │ │ struct timeval pertry_timeout, total_timeout; │ │ struct sockaddr_in server_addr; │ │ int addrlen, sock = RPC_ANYSOCK; │ │ register CLIENT *client; │ │ enum clnt_stat clnt_stat; │ │ char buf[1000]; │ │ char *s = buf; │ │ │ └────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────┐ │ │ │ /* │ │ * начало в примере 3.3 │ │ */ │ │ if ((client = clnttcp_create(&server_addr, WINDOWPROG, │ │ WINDOWVERS, &sock, 0, 0)) == NULL) { │ │ perror("clnttcp_create"); │ │ exit(-1); │ │ } │ │ total_timeout.tv_sec = 0; │ │ total_timeout.tv_usec = 0; │ │ while (scanf("%s", s) != EOF) { │ │ clnt_stat = clnt_call(client, RENDERSTRING_BATCHED, │ │ xdr_wrapstring, &s, NULL, NULL, total_timeout); │ │ if (clnt_stat != RPC_SUCCESS) { │ │ clnt_perror(client, "batched rpc"); │ │ exit(-1); │ │ } │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────┐ │ │ │ /* │ │ * останов конвейера │ │ */ │ │ total_timeout.tv_sec = 20; │ │ clnt_stat = clnt_call(client, NULLPROC, │ │ xdr_void, NULL, xdr_void, NULL, total_timeout); │ │ if (clnt_stat != RPC_SUCCESS) { │ │ clnt_perror(client, "rpc"); │ │ exit(-1); │ │ } │ │ │ │ clnt_destroy(client); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Поскольку сервер не посылает сообщений, клиенты не ставятся в известность относительно всех происходящих сбоев. Таким образом, клиенты решают сами, когда им переходить к обработке ошибок. Вышеприведенный пример закончился обработкой 2000 строк файла /etc/termcap. В данном случае функция обработки оставила строки в первоначальном виде. Пример был опробован на четырех конфигураци- ях с получением следующих результатов: Конфигурация Синхронизация (в секундах) машина к самой себе, регулярный RPC 50 машина к самой себе, пакетный RPC 16 машина к другой машине, регулярный RPC 52 машина к другой машине, пакетный RPC 10 Выполнение fscanf() по отношению к /etc/termcap требует толь- ко 6 секунд. Налицо преимущество протоколов, которые обеспечивают параллельное выполнение. Установление подлинности В ранее рассмотренных примерах вызывающий процесс никогда не идентифицировал себя перед сервером, а сервер никогда и не требо- вал от него указания кода идентификации. Однако, некоторые сете- вые функции, такие как сетевая файловая система, используют более строгие меры защиты данных, по сравнению с представленными ранее. В действительности пакет RPC устанавливает подлинность каждо- го RPC-запроса на серверном конце, и клиент вынужден генерировать и посылать параметры идентификации. Подобно тому, как при созда- нии клиентов и серверов RPC могут использоваться различные транс- портные механизмы (TCP/IP или UDP/IP), с клиентами RPC могут быть связаны различные формы установления подлинности; умолчанием на форму является тип none. Подсистема установления подлинности, входящая в пакет RPC, является расширяемой. Это означает, что в ней облегчается под- держка многочисленных форм идентификации. В настоящем же разделе мы коснемся только вопросов идентификации в операционной среде XENIX и UNIX. Клиент Когда вызывающий процесс создает новый выход на клиента RPC, например: clnt = clntudp_create(address, prognum, versnum, wait, sockp) соответствующий транспортный механизм устанавливает умолчание на форму идентификации: clnt->cl_auth = authnone_create(); Клиент RPC может выбрать среду идентификации (XENIX или UNIX) установкой clnt->cl_auth после создания выхода: clnt->cl_auth = authunix_create_default(); В результате каждый RPC-запрос, ассоциированный с клиентом clnt, имеет следующую структуру "верительных грамот": ┌────────────────────────────────────────────────────────────┐ │ │ │ /* │ │ * для UNIX │ │ */ │ │ struct authunix_parms { │ │ ulong aup_time; /* время создания */ │ │ char *aup_machname; /* host-имя клиентской маши-│ │ * ны */ │ │ int aup_uid; /* исполнительный код иден- │ │ /* тификации UNIX для клиен-│ │ * та */ │ │ int aup_gid; /* текущий групповой код */ │ │ uint aup_len; /* длина элемента массива │ │ * aup_gids */ │ │ int *aup_gids; /* массив групп, к которым │ │ * принадлежит пользова- │ │ * тель */ │ │ }; │ │ │ └────────────────────────────────────────────────────────────┘ Значения этих полей устанавливаются функцией authunix_create_ default(). Пользователь RPC, создавший новую форму идентификации, отве- чает за ее удаление, которое производится строкой: auth_destroy(clnt->cl_auth); Это необходимо делать для того, чтобы освободить память. Сервер Пакет RPC посылает передающей процедуре запрос, связанный с произвольной формой идентификации, что создает трудности для раз- работчиков процедуры. Рассмотрим для примера структуру полей по- сылаемого запроса: ┌────────────────────────────────────────────────────────────┐ │ │ │ /* │ │ * Запрос процедуре RPC │ │ */ │ │ struct svc_req { │ │ ulong rq_prog; /* номер обслуживающей про- │ │ * граммы */ │ │ ulong rq_vers; /* номер версии обслуживающе- │ │ * го протокола */ │ │ ulong rq_proc; /* номер процедуры */ │ │ struct opaque_auth rq_cred; /* "верительные грамо- │ │ * ты" необработанные *│ │ caddr_t rq_clntcred; /* только для чтения, │ │ * "верительные грамоты", │ │ * прошедшие обработку */ │ │ }; │ │ │ └────────────────────────────────────────────────────────────┘ Структура описания формы идентификации: ┌────────────────────────────────────────────────────────────┐ │ │ │ /* │ │ * Информация о форме идентификации │ │ */ │ │ struct opaque_auth { │ │ enum_t oa_flavor; /* форма "верительных грамот" │ │ caddr_t oa_base; /* адрес */ │ │ uint oa_length; /* не должна превышать │ │ * MAX_AUTH_BYTES */ │ │ }; │ │ │ └────────────────────────────────────────────────────────────┘ Пакет RPC гарантирует передающей процедуре, что: * "Верительные грамоты" (rq_cred), содержащиеся в запросе, хорошо сформатированы. Благодаря этому разработчик процеду- ры может по значению rq_cred.oa_flavor определить использу- емую форму идентификации. Он также может проверить значения остальных полей структуры rq_cred, если данная форма не поддерживается пакетом RPC. * Значение поля rq_clntcred в запросе либо NULL, либо указы- вает на сформатированную структуру, соответствующую поддер- живаемой форме идентификации запроса. Поскольку в настоящее время поддерживаются только формы, соответствующие среде XENIX и UNIX, rq_clntcred может указывать только на струк- туру authunix_parms. Если rq_clntcred имеет значение NULL, разработчик процедуры может проверять значения остальных полей структуры rq_cred, заботясь о том, чтобы процедура знала все о новой форме идентификации, не известной пакету RPC. Пример удаленной процедуры можно расширить так, чтобы резуль- таты вычислялись для всех пользователей, кроме пользователя с ко- дом идентификации 16: ┌────────────────────────────────────────────────────────────┐ │ │ │ nuser(rqstp, transp) │ │ struct svc_req *rqstp; │ │ SVCXPRT *transp; │ │ { │ │ struct authunix_parms *unix_cred; │ │ int uid; │ │ unsigned long nusers; │ │ /* │ │ * мы не заботимся об идентификации для null-процедуры │ │ */ │ │ if (rqstp->rq_proc == NULLPROC) { │ │ if (!svc_sendreply(transp, xdr_void, 0)) { │ │ fprintf(stderr, "could not reply to RPC call\n");│ │ exit(1); │ │ } │ │ return; │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────┐ │ │ │ /* │ │ * получение кода идентификации │ │ */ │ │ switch (rqstp->rq_cred.oa_flavor) { │ │ case AUTH_UNIX: │ │ unix_cred = (struct authunix_parms *) rqstp->rq_clntcred;│ │ uid = unix_cred->aup_uid; │ │ break; │ │ case AUTH_NULL: │ │ default: │ │ svcerr_weakauth(transp); │ │ return; │ │ } │ │ switch (rqstp->rq_proc) { │ │ case RUSERSPROC_NUM; │ │ │ └────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────┐ │ │ │ /* │ │ * вызывающий процесс должен иметь разрешение на │ │ * вызов этой процедуры │ │ */ │ │ if (uid == 16) { │ │ svcerr_systemerr(transp); │ │ return; │ │ } │ │ /* │ │ * текст вычисления числа пользователей │ │ * с засылкой результата в переменную nusers │ │ */ │ │ if (!svc_sendreply(transp, xdr_u_long, &nusers) { │ │ fprintf(stderr, "could not reply to RPC call\n");│ │ exit(1); │ │ } │ │ return; │ │ default: │ │ svcerr_noproc(transp); │ │ return; │ │ } │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Заметим следующее: * Параметры идентификации, связанные с NULLPROC (процедура с нулевым номером), обычно не проверяются. * Если тип параметра идентификации не обрабатывается процеду- рой, обратитесь к svcerr_weakauth(). * Протокол обслуживания сам по себе не сообщает код состояния в случае отказа при обращении. В рассмотренном выше примере протокол не имеет такого кода, поэтому вызывается svcerr_systemerr(). Последний пункт подчеркивает взаимосвязь между пакетом иден- тификации RPC и процедурами; RPC имеет дело только с идентифика- цией, но не с управлением индивидуальным доступом к процедурам. Процедуры должны реализовывать свои собственные алгоритмы управ- ления доступом, анализируя последние по коду возврата. Поддержка разных версий одной программы По условию номер первой версии программы FOO содержится в FOOVERS_ORIG, а номер последней - в FOOVERS. Предположим, что но- вая версия программы user возвращает значение типа unsigned short, а не типа long. Если назвать эту версию RUSERSVERS_SHORT, то сервер, обеспечивающий поддержку обеих версий, должен вести двойную регистрацию. ┌────────────────────────────────────────────────────────────┐ │ │ │ if (!svc_register(transp, RUSERSPROG, RUSERSVERS_ORIG, │ │ nuser, IPPROTO_TCP)) { │ │ fprintf(stderr, "could not register RUSER service\n");│ │ exit(1); │ │ } │ │ if (!svc_register(transp, RUSERSPROG, RUSERSVERS_SHORT, │ │ nuser, IPPROTO_TCP)) { │ │ fprintf(stderr, "could not register RUSER service\n");│ │ exit(1); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────┐ │ │ │ nuser(rqstp, transp) │ │ struct svc_req *rqstp; │ │ SVCXPRT *transp; │ │ { │ │ unsigned long nusers; │ │ unsigned short nusers2; │ │ │ │ switch (rqstp->rq_proc) { │ │ case NULLPROC: │ │ if (!svc_sendreply(transp, xdr_void, 0)) { │ │ fprintf(stderr, "could not reply to RPC call\n");│ │ exit(1); │ │ } │ │ return; │ │ case RUSERSPROC_NUM: │ │ /* │ │ * текст вычисления числа пользователей │ │ * с размещением результата в переменной nusers │ │ */ │ │ nusers2 = nusers; │ │ if (rqstp->rq_vers == RUSERSVERS_ORIG) │ │ if (!svc_sendreply(transp,xdr_u_long,&nusers)) {│ │ fprintf(stderr,"could not reply to RPC call\n");│ │ exit(1); │ │ } │ │ else │ │ if (!svc_sendreply(transp,xdr_u_short,&nusers2)) {│ │ fprintf(stderr,"could not reply to RPC call\n");│ │ exit(1); │ │ return; │ │ default: │ │ svcerr_noproc(transp); │ │ return; │ │ } │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Проведение сериализации и десериализации разными способами Рассмотрим пример, в существе своем описывающий rcp. Инициа- тор функции snd() получает данные из стандартного ввода и посыла- ет их серверу rcv(), который выводит их в стандартный вывод. RPC- запрос использует протокол TCP. Здесь также рассматривается XDR-процедура, производящая сериализацию по другому принципу, не- жели десериализацию. XDR-процедура ┌────────────────────────────────────────────────────────────┐ │ │ │ /* │ │ * XDR-процедура: │ │ * │ │ * при декодировании читает из канала, записывает в fp │ │ * при кодировании читает из fp, записывает в канал │ │ */ │ │ #include │ │ #include │ │ │ │ xdr_rcp(xdrs, fp) │ │ XDR *xdrs; │ │ FILE *fp; │ │ { │ │ unsigned long size; │ │ char buf[MAXCHUNK], *p; │ │ │ │ if (xdrs->x_op == XDR_FREE) /* ничего не освобождается */│ │ return 1; │ │ while (1) { │ │ if (xdrs->x_op == XDR_ENCODE) { │ │ if ((size = fread(buf,sizeof(char),MAXCHUNK,fp))│ │ == 0 && ferror(fp)) { │ │ fprintf(stderr, "could not fread\n"); │ │ exit(1); │ │ } │ │ } │ │ p = buf; │ │ if (!xdr_bytes(xdrs,&p,&size,MAXCHUNK)) │ │ return(0); │ │ if (size == 0) │ │ return(1); │ │ if (xdrs->x_op == XDR_DECODE) { │ │ if (fwrite(buf,sizeof(char),size,fp) != size) { │ │ fprintf(stderr, "could not fwrite\n"); │ │ exit(1); │ │ } │ │ } │ │ } │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Процедуры отправителя ┌────────────────────────────────────────────────────────────┐ │ │ │ /* │ │ * Процедуры отправителя │ │ */ │ │ #include │ │ #include │ │ #include │ │ #include │ │ #include │ │ │ │ main(argc, argv) │ │ int argc; │ │ char **argv; │ │ { │ │ int err; │ │ │ │ if (argc < 2) { │ │ fprintf(stderr,"usage: %s server-name\n",argv[0]);│ │ exit(-1); │ │ } │ │ if ((err = callrpctcp(argv[1],RCPPROG,RCPPROC_FP, │ │ RCPVERS,xdr_rcp,stdin,xdr_void,0)) != 0) { │ │ clnt_perrno(err); │ │ fprintf(stderr, "could not make RPC call\n"); │ │ exit(1); │ │ } │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────┐ │ │ │ callrpctcp(host,prognum,procnum,versnum,inproc,in,outproc, │ │ out) │ │ char *host, *in, *out; │ │ xdrproc_t inproc, outproc; │ │ { │ │ struct sockaddr_in server_addr; │ │ int socket = RPC_ANYSOCK; │ │ enum clnt_stat clnt_stat; │ │ struct hostent *hp; │ │ register CLIENT *client; │ │ struct timeval total_timeout; │ │ │ │ if ((hp = gethostbyname(host)) == NULL) { │ │ fprintf(stderr,"cannot get addr for '%s'\n",host);│ │ exit(-1); │ │ } │ │ bcopy(hp->h_addr,(caddr_t)&server_addr.sin_addr, │ │ hp->h_length); │ │ server_addr.sin_family = AF_INET; │ │ server_addr.sin_port = 0; │ │ if ((client = clnttcp_create(&server_addr, prognum, │ │ versnum, &socket, BUFSIZ, BUFSIZ)) == NULL) { │ │ perror("rcptcp_create"); │ │ exit(-1); │ │ } │ │ total_timeout.tv_sec = 20; │ │ total_timeout.tv_usec = 0; │ │ clnt_stat = clnt_call(client, procnum, inproc, in, │ │ outproc, out, total_timeout); │ │ clnt_destroy(client); │ │ return ((int)clnt_stat); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Процедуры получателя ┌────────────────────────────────────────────────────────────┐ │ │ │ /* │ │ * Процедуры получателя │ │ */ │ │ #include │ │ #include │ │ │ │ main() │ │ { │ │ register SVCPRT *transp; │ │ if ((transp = svctcp_create(RPC_ANYSOCK,1024,1024)) == │ │ == NULL) { │ │ fprintf("svctcp_create: error\n"); │ │ exit(1); │ │ } │ │ pmap_unset(RPCPROG, RPCVERS); │ │ if (!svc_register(transp,RCPPROG,RCPVERS,rcp_service, │ │ IPPROTO_TCP)) { │ │ fprintf(stderr, "svc_register: error\n"); │ │ exit(1); │ │ } │ │ svc_run(); │ │ fprintf(stderr,"svc_run should never return\n"); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────┐ │ │ │ rcp_service(rqstp, transp) │ │ register struct svc_req *rqstp; │ │ register SVCPRT *transp; │ │ { │ │ switch (rqstp->rq_proc) { │ │ case NULLPROC: │ │ if (svc_sendreply(transp, xdr_void, 0) == 0) { │ │ fprintf(stderr, "err: rcp_service"); │ │ exit(1); │ │ } │ │ return; │ │ case RCPPROC_FP: │ │ if (!svc_getargs(transp, xdr_rcp, stdout)) { │ │ svcerr_decode(transp); │ │ return; │ │ } │ │ if (!svc_sendreply(transp, xdr_void, 0)) { │ │ fprintf(stderr, "cannot reply\n"); │ │ return; │ │ } │ │ exit(0); │ │ default: │ │ svcerr_noproc(transp); │ │ return; │ │ } │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Использование процедур обратного вызова Иногда бывает необходимо превратить сервера в клиента, обра- щающегося с RPC-запросом к своему клиенту. Примером такой ситуа- ции является удаленная отладка, при которой клиентом выступает система управления окнами, а сервером - отладчик, работающий на удаленной машине. Большую часть времени пользователь управляет с помощью устройства ввода координат (мыши) отладочным окном, пре- образующим нажатие клавиши в команду отладчика и RPC-запрос к серверу (на котором работает отладчик), приказывающий последнему исполнить эту команду. Однако, когда отладчик достигает точки прерывания, роли меняются и отладчик обращается с RPC-запросом к программе управления окном, информируя тем самым пользователя о достижении контрольной точки. Для того, чтобы сделать RPC-запрос, нужно знать номер прог- раммы. Поскольку этот номер генерируется динамически, он должен располагаться в диапазоне для нерезидентных программ (0x40000000 - 0x5fffffff). Подпрограмма gettransient() возвращает номер из этого диапазона, под которым регистрирует программу с отображени- ем портов. Запрос к pmap_set() (отображателю портов) является операцией типа "проверить и установить". По завершении аргумент sockp будет описывать гнездо, используемое в подпрограммах svcudp _create() или svctcp_create(). ┌────────────────────────────────────────────────────────────┐ │ │ │ #include │ │ #include │ │ #include │ │ │ │ gettransient(proto, vers, sockp) │ │ int *sockp; │ │ { │ │ static int prognum = 0x40000000; │ │ int s, len, socktype; │ │ struct sockaddr_in addr; │ │ │ │ switch(proto) { │ │ case IPPROTO_UDP: │ │ socktype = SOCK_DGRAM; │ │ break; │ │ case IPPROTO_TCP: │ │ socktype = SOCK_STREAM; │ │ break; │ │ default: │ │ fprintf(stderr,"unknown protocol type\n");│ │ return 0; │ │ } │ │ if (*sockp == RPC_ANYSOCK) { │ │ if ((s = socket(AF_INET, socktype, 0)) < 0) { │ │ perror("socket"); │ │ return(0); │ │ } │ │ *sockp = s; │ │ } │ │ else │ │ s = *sockp; │ │ addr.sin_addr.s_addr = 0; │ │ addr.sin_family = AF_INET; │ │ addr.sin_port = 0; │ │ len = sizeof(addr); │ │ /* │ │ * может быть уже привязан, поэтому ошибка не проверя- │ │ * ется │ │ */ │ │ (void) bind(s, &addr, len); │ │ if (getsockname(s, &addr, &len) < 0) { │ │ perror("getsockname"); │ │ return(0); │ │ } │ │ while (pmap_set(prognum++,vers,proto,addr.sin_port) == │ │ == 0) │ │ continue; │ │ return (prognum-1); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Следующая пара программ иллюстрирует использование процедуры gettransient(). Клиент обращается к серверу с RPC-запросом, пере- давая ему номер нерезидентной программы и ожидая обратного запро- са сервера под тем же номером. Сервер регистрирует программу EXAMPLEPROG, поэтому в обратном запросе будет использоваться ее программный номер. Через некоторое случайное время (по получении сигнала ALRM) сервер посылает обратный RPC-запрос, используя программный номер, полученный ранее. Программа клиента ┌────────────────────────────────────────────────────────────┐ │ │ │ /* │ │ * клиент │ │ */ │ │ #include │ │ #include │ │ │ │ int callback(); │ │ char hostname[256]; │ │ │ │ main(argc, argv) │ │ char **argv; │ │ { │ │ int x, ans, s; │ │ SVCXPRT *xprt; │ │ │ │ gethostname(hostname, sizeof(hostname)); │ │ s = RPC_ANYSOCK; │ │ x = gettransient(IPPROTO_UDP, 1, &s); │ │ fprintf(stderr, "client gets prognum %d\n", x); │ │ │ │ if ((xprt = svcudp_create(s)) == NULL) { │ │ fprintf(stderr,"rpc_server: svcudp_create\n"); │ │ exit(1); │ │ } │ │ (void)svc_register(xprt,x,1,callback,0); │ │ │ │ ans = callrpc(hostname, EXAMPLEPROG, │ │ EXAMPLEPROC_CALLBACK, EXAMPLEVERS, xdr_int, &x, │ │ xdr_void, 0); │ │ if (ans != 0) { │ │ fprintf(stderr, "call: "); │ │ clnt_perrno(ans); │ │ fprintf(stderr, "\n"); │ │ } │ │ svc_run(); │ │ fprintf(stderr, │ │ "Error: svc_run should not have returned\n"); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────┐ │ │ │ callback(rqstp, transp) │ │ register struct svc_req *rqstp; │ │ register SVCXPRT *transp; │ │ { │ │ switch (rqstp->rq_proc) { │ │ case 0: │ │ if (!svc_sendreply(transp,xdr_void,0)) {│ │ fprintf(stderr,"err: rusersd\n"); │ │ exit(1); │ │ } │ │ exit(0); │ │ case 1: │ │ if (!svc_getargs(transp,xdr_void,0)) { │ │ svcerr_decode(transp); │ │ exit(1); │ │ } │ │ fprintf(stderr,"client got callback\n");│ │ if (!svc_sendreply(transp,xdr_void,0)) {│ │ fprintf(stderr,"err: rusersd\n"); │ │ exit(1); │ │ } │ │ } │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Программа сервера ┌────────────────────────────────────────────────────────────┐ │ │ │ /* │ │ * сервер │ │ */ │ │ #include │ │ #include │ │ #include │ │ │ │ char *getnewprog(); │ │ char hostname[256]; │ │ int docallback(); │ │ int pnum; /* номер программы для процедуры обрат- │ │ * ного вызова */ │ │ main(argc, argv) │ │ char **argv; │ │ { │ │ gethostname(hostname,sizeof(hostname)); │ │ registerrpc(EXAMPLEPROG,EXAMPLEPROC_CALLBACK, │ │ EXAMPLEVERS, getnewprog, xdr_int, xdr_void);│ │ fprintf(stderr,"server going into svc_run\n"); │ │ alarm(10); │ │ signal(SIGALRM, docallback); │ │ svc_run(); │ │ fprintf(stderr, │ │ "Error: svc_run should not have returned\n"); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────┐ │ │ │ char * │ │ getnewprog(pnump) │ │ char *pnump; │ │ { │ │ pnum = *(int *)pnump; │ │ return NULL; │ │ } │ │ docallback() │ │ { │ │ int ans; │ │ │ │ ans = callrpc(hostname,pnum,1,1,xdr_void,0,xdr_void, │ │ 0); │ │ if (ans != 0) { │ │ fprintf(stderr, "server: "); │ │ clnt_perrno(ans); │ │ fprintf(stderr, "\n"); │ │ } │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограммы RPC ───────────────────────────────────────────────────────────── В данном разделе описывается синтаксис и назначение каждой из подпрограмм RPC. Подпрограммы рассматриваются в алфавитном поряд- ке. auth_destroy() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ auth_destroy(auth) │ │ AUTH *auth; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма уничтожает идентификационную информацию, ассоци- ированную с идентификатором auth. При этом обычно освобождаются локальные структуры данных. После вызова auth_destroy() перемен- ная auth становится неопределенной. authnone_create() ┌────────────────────────────────────────────────────────────┐ │ │ │ AUTH * │ │ authnone_create() │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма создает и возвращает идентифицируемый выход на RPC, не передающий при вызове удаленной процедуры никакой инфор- мации, связанной с установлением подлинности. authunix_create() ┌────────────────────────────────────────────────────────────┐ │ │ │ AUTH * │ │ authunix_create(host, uid, gid, len, aup_gids) │ │ char *host; │ │ int uid, gid, len, *aup_gids; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма создает и возвращает идентифицируемый выход на RPC, содержащий информацию, связанную с установлением подлиннос- ти, в формате, принятом в ОС XENIX и UNIX. Параметр host является именем машины, на которой была создана информация; uid - код идентификации пользователя; gid - код идентификации группы теку- щего пользователя; len и aup_gids относятся к массиву групп, к которому принадлежит пользователь. ┌────────────────────────────────────────────────────────────┐ │ │ │ AUTH * │ │ authunix_create_default() │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма вызывает authunix_create() с соответствующими параметрами. callrpc() ┌────────────────────────────────────────────────────────────┐ │ │ │ callrpc(host,prognum,versnum,procnum,inproc,in,outproc,out)│ │ char *host; │ │ ulong prognum, versnum, procnum; │ │ char *in, *out; │ │ xdrproc_t inproc, outproc; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма обращается к удаленной процедуре, которая описы- вается параметрами prognum, versnum и procnum на машине host. Па- раметр in является адресом аргументов процедуры, out - адрес, по которому будут размещены результаты; inproc используется для ко- дирования параметров процедуры, а outproc для декодирования ее результатов. В случае успеха код возврата процедуры равен нулю, в против- ном случае значению enum clnt_stat, приведенному к целому типу. Подпрограмма clnt_perrno() переводит коды состояний в сообщения. ----------------------------------------------------------------- Замечание. При вызове удаленных процедур подпрограмма использует в ка- честве транспортного механизма протокол UDP/IP. Ограничения см. в разделе clntudp_create(). ----------------------------------------------------------------- clnt_broadcast() ┌────────────────────────────────────────────────────────────┐ │ │ │ enum clnt_stat │ │ clnt_broadcast(prognum, versnum, procnum, │ │ inproc, in, outproc, out, eachresult) │ │ │ │ ulong prognum, versnum, procnum; │ │ char *in, *out; │ │ xdrproc_t inproc, outproc; │ │ resultproc_t eachresult; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма работает подобно callrpc(), за исключением того, что запрос является широковещательным и касается всех объединен- ных в сеть локальных станций. При каждом получении ответа подп- рограмма обращается к eachresult: ┌────────────────────────────────────────────────────────────┐ │ │ │ eachresult(out, addr) │ │ char *out; │ │ struct sockaddr_in *addr; │ │ │ └────────────────────────────────────────────────────────────┘ где out имеет то же значение, что и в clnt_broadcast(), но каса- ется размещения результатов удаленной процедуры; addr указывает на адрес машины, посылающей результаты. Если eachresult() возвращает ноль, clnt_broadcast() ждет дру- гие ответы; в противном случае подпрограмма прекращает работу с индикацией соответствующего статуса. clnt_call() ┌────────────────────────────────────────────────────────────┐ │ │ │ enum clnt_stat │ │ clnt_call(clnt, procnum, inproc, out, tout) │ │ CLIENT *clnt; long procnum; │ │ xdrproc_t inproc, outproc; │ │ char *in, *out; │ │ struct timeval tout; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма обращается к удаленной процедуре procnum, свя- занной с клиентским выходом clnt, который создается соответствую- щей процедурой RPC (такой как clntudp_create). Параметр in явля- ется адресом аргументов процедуры, out - адресом размещения результатов; inproc используется для кодирования параметров про- цедуры, а outproc для декодирования ее результатов; tout - допус- тимое время ожидания возвращения результатов. clnt_destroy() ┌────────────────────────────────────────────────────────────┐ │ │ │ clnt_destroy(clnt) │ │ CLIENT *clnt; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма уничтожает клиентский выход на RPC. При этом обычно освобождаются локальные структуры данных, включая самого clnt. После вызова clnt_destroy() переменная clnt становится не- определенной. ----------------------------------------------------------------- Замечание. Подпрограммы удаления клиента не закрывают гнезда, связанные с clnt. Ответственность за их закрытие возлагается на пользовате- ля. ----------------------------------------------------------------- clnt_freeres() ┌────────────────────────────────────────────────────────────┐ │ │ │ clnt_freeres(clnt, outproc, out) │ │ CLIENT *clnt; │ │ xdrproc_t outproc; │ │ char *out; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма освобождает все структуры данных, выделенные системой RPC/XDR во время декодирования результатов RPC-запроса. Параметр out является адресом результатов, outproc - XDR-процеду- рой, описывающей результаты на элементарном уровне. В случае успешного освобождения код возврата подпрограммы ра- вен 1, в противном случае - нулю. clnt_geterr() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ clnt_geterr(clnt, errp) │ │ CLIENT *clnt; │ │ struct rpc_err *errp; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма копирует структуру описания ошибок по адресу errp. clnt_pcreaterror() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ clnt_pcreateerror(s) │ │ char *s; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма выводит сообщение (в файл ошибок) о причине, по которой клиентский выход на RPC не может быть создан. Сообщение предваряется строкой s и двоеточием. clnt_perrno() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ clnt_perrno(stat) │ │ enum clnt_stat stat; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма выводит в файл ошибок сообщение, соответствующее условию stat. clnt_perror() ┌────────────────────────────────────────────────────────────┐ │ │ │ clnt_perror(clnt, s) │ │ CLIENT *clnt; │ │ char *s; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма выводит в файл ошибок сообщение о причине неу- дачного завершения RPC-запроса; clnt - выход, используемый для запроса. Сообщение предваряется строкой s и двоеточием. clntraw_create() ┌────────────────────────────────────────────────────────────┐ │ │ │ CLIENT * │ │ clntraw_create(prognum, versnum) │ │ ulong prognum, versnum; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма создает модель клиента RPC для удаленной прог- раммы prognum версии versnum. Транспортный механизм, используемый для передачи сообщений, представляет собой буфер в адресном пространстве процесса, так что соответствующий сервер RPC будет работать в том же самом пространстве; см. svcraw_create(). Это позволяет моделировать RPC и собирать накладные расходы RPC (нап- ример, общее время передачи) вместе, без какого-либо вмешательст- ва ядра. В случае неудачи код возврата подпрограммы - NULL. clnttcp_create() ┌────────────────────────────────────────────────────────────┐ │ │ │ CLIENT * │ │ clnttcp_create(addr,prognum,versnum,sockp,sendsz,recvsz) │ │ struct sockaddr_in *addr; │ │ ulong prognum, versnum; │ │ int *sockp; │ │ uint sendsz, recvsz; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма создает клиента RPC для удаленной программы prognum версии versnum; в качестве транспортного механизма клиент использует протокол TCP/IP. Удаленная программа привязывается к межсетевому адресу *addr. Если addr->sin_port = 0, он устанавли- вается таким образом, чтобы указывать на порт, из которого прини- мает данные удаленная программа. Эту информацию дает удаленная функция portmap. Параметр *sockp соответствует гнезду; если он имеет значение RPC_ANYSOCK, подпрограмма открывает новое гнездо. Поскольку в RPC на базе TCP используется буферизованный ввод-вывод, пользователь в параметрах sendsz и recvsz может задавать размеры буферов пере- дачи и приема. В случае неудачи код возврата подпрограммы - NULL. clntudp_create() ┌────────────────────────────────────────────────────────────┐ │ │ │ CLIENT * │ │ clntudp_create(addr, prognum, versnum, wait, sockp) │ │ struct sockaddr_in *addr; │ │ ulong prognum, versnum; │ │ struct timeval wait; │ │ int *sockp; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма создает клиента RPC для удаленной программы prognum версии versnum; в качестве транспортного механизма клиент использует протокол UDP/IP. Удаленная программа привязывается к межсетевому адресу *addr. Если addr->sin_port = 0, он устанавли- вается таким образом, чтобы указывать на порт, из которого прини- мает данные удаленная программа (эту информацию дает удаленная функция portmap). Параметр *sockp соответствует гнезду; если он имеет значение RPC_ANYSOCK, подпрограмма открывает новое гнездо. Транспортный протокол UDP повторяет RPC-запрос через интервалы времени, кратные значению wait, до получения ответа или истечения таймаута. ----------------------------------------------------------------- Замечание. Поскольку в RPC на базе UDP сообщения могут включать в себя от 4 до 8 Кбайт закодированной информации, этот механизм не может использоваться процедурами, которые имеют громоздкие аргументы или результаты. ----------------------------------------------------------------- get_myaddress() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ get_myaddress(addr) │ │ struct sockaddr_in *addr; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма устанавливает адрес машины равным *addr, не об- ращаясь к библиотечным процедурам, имеющим дело с файлом /etc/hosts. Номер порта всегда приравнивается к htons(PMAPPORT). pmap_getmaps() ┌────────────────────────────────────────────────────────────┐ │ │ │ struct pmaplist * │ │ pmap_getmaps(addr) │ │ struct sockaddr_in *addr; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма, выполняющая роль пользовательского интерфейса по отношению к функции portmap, возвращает перечень имеющихся со- ответствий между номерами RPC-программ и портами на host-машине, привязанной к адресу *addr. Код возврата может иметь значение NULL. Эта подпрограмма используется командой rpcinfo -p. pmap_getport() ┌────────────────────────────────────────────────────────────┐ │ │ │ ushort │ │ pmap_getport(addr, prognum, versnum, protocol) │ │ struct sockaddr_in *addr; │ │ ulong prognum, versnum, protocol; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма, выполняющая роль пользовательского интерфейса по отношению к функции portmap, возвращает номер порта для прог- раммы с номером prognum и версией versnum. Нулевое значение кода возврата означает отсутствие искомого соответствия или невозмож- ность установления контакта между системой RPC и удаленной функ- цией portmap. В последнем случае в глобальную переменную rpc_createerr заносится статус RPC. pmap_rmtcall() ┌────────────────────────────────────────────────────────────┐ │ │ │ enum clnt_stat │ │ pmap_rmtcall(addr, prognum, versnum, procnum, │ │ inproc, in, outproc, out, tout, portp) │ │ struct sockaddr_in *addr; │ │ ulong prognum, versnum, procnum; │ │ char *in, *out; │ │ xdrproc_t inproc, outproc; │ │ struct timeval tout; │ │ ulong *portp; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма, выполняющая роль пользовательского интерфейса по отношению к функции portmap, приказывает portmap на host-маши- не с адресом *addr сделать RPC-запрос от имени пользователя к процедуре, работающей на этом host'е. Параметр *portp в случае успешного выполнения процедуры превращается в номер порта прог- раммы. Определения остальных параметров даны в тексте, посвящен- ном описанию callrpc() и clnt_call(); см. также clnt_broadcast(). pmap_set() ┌────────────────────────────────────────────────────────────┐ │ │ │ pmap_set(prognum, versnum, protocol, port) │ │ ulong prognum, versnum, protocol; │ │ ushort port; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма, выполняющая роль пользовательского интерфейса по отношению к функции portmap, устанавливает соответствие между тройкой [prognum, versnum, protocol] и значением port. Наиболее вероятные идентификаторы протокола - IPPROTO_UDP и IPPROTO_TCP. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. pmap_unset() ┌────────────────────────────────────────────────────────────┐ │ │ │ pmap_unset(prognum, versnum) │ │ ulong prognum, versnum; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма, выполняющая роль пользовательского интерфейса по отношению к функции portmap, отменяет все соответствия между тройкой [prognum, versnum, *] и значением port. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. registerrpc() ┌────────────────────────────────────────────────────────────┐ │ │ │ registerrpc(prognum, versnum, procnum, procname, inproc, │ │ outproc) │ │ ulong prognum, versnum, procnum; │ │ char *(*procname) (); │ │ xdrproc_t inproc, outproc; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма регистрирует процедуру procname с функциональным пакетом RPC. Если поступающий запрос касается программы prognum версии versnum и процедуры procnum, имя процедуры procname увязы- вается с указателем на ее параметры; progname возвращает указа- тель на место расположения результатов; inproc используется для декодирования параметров, а outproc - для кодирования результа- тов. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. ----------------------------------------------------------------- Замечание. Доступ к удаленным процедурам, зарегистрированным в такой форме, обеспечивается через механизм UDP/IP. Ограничения см. в svcudp_create(). ----------------------------------------------------------------- rpc_createerr ┌────────────────────────────────────────────────────────────┐ │ │ │ struct rpc_createerr rpc_createerr; │ │ │ └────────────────────────────────────────────────────────────┘ Глобальная переменная, значение которой устанавливается одной из процедур создания клиента RPC, завершившейся неудачно. Для раскрытия причины воспользуйтесь подпрограммой clnt_pcreateerror(). svc_destroy() ┌────────────────────────────────────────────────────────────┐ │ │ │ svc_destroy(xprt) │ │ SVCXPRT *xprt; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма отменяет выход на транспортный механизм RPC xprt. При этом обычно освобождаются локальные структуры данных, включая сам xprt. После вызова подпрограммы переменная xprt ста- новится неопределенной. svc_fds ┌────────────────────────────────────────────────────────────┐ │ │ │ int svc_fds; │ │ │ └────────────────────────────────────────────────────────────┘ Глобальная переменная, определяющая двоичную маску дескрипто- ра чтения механизма RPC; может использоваться в качестве парамет- ра системной функции select. Представляет интерес только в том случае, если разработчик не обращается к svc_run(), а выполняет обработку асинхронного события самостоятельно. Переменная доступ- на только для чтения, однако ее значение может изменяться после выполнения svc_getreq() или любой из процедур создания. svc_freeargs() ┌────────────────────────────────────────────────────────────┐ │ │ │ svc_freeargs(xprt, inproc, in) │ │ SVCXPRT *xprt; │ │ xdrproc_t inproc; │ │ char *in; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма освобождает структуры данных, выделенные систе- мой RPC/XDR при декодировании аргументов сервисной процедуры с помощью svc_getargs(). В случае успешного освобождения код возврата подпрограммы ра- вен 1, в противном случае - нулю. svc_getargs() ┌────────────────────────────────────────────────────────────┐ │ │ │ svc_getargs(xprt, inproc, in) │ │ SVCXPRT *xprt; │ │ xdrproc_t inproc; │ │ char *in; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма декодирует аргументы RPC-запроса, ассоциирован- ного с выходом на транспортный механизм RPC xprt. Параметр in яв- ляется адресом, по которому размещаются аргументы; inproc - XDR-процедура, используемая для декодирования аргументов. В случае успешного декодирования код возврата подпрограммы равен 1, в противном случае - нулю. svc_getcaller() ┌────────────────────────────────────────────────────────────┐ │ │ │ struct sockaddr_in │ │ svc_getcaller(xprt) │ │ SVCXPRT *xprt; │ │ │ └────────────────────────────────────────────────────────────┘ Рекомендуемый способ получения сетевого адреса процесса, вы- зывающего процедуру, ассоциированную с выходом на транспортный механизм RPC xprt. svc_getreq() ┌────────────────────────────────────────────────────────────┐ │ │ │ svc_getreq(rdfds) │ │ int rdfds; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма представляет интерес только в том случае, если разработчик не обращается к svc_run(), а выполняет обработку асинхронного события самостоятельно. Вызывается в том случае, когда согласно системной функции select на некоторые гнезда RPC поступает RPC-запрос; rdfds - двоичная маска дескриптора чтения. Подпрограмма завершает работу после обслуживания всех гнезд, свя- занных со значением rdfds. svc_register() ┌────────────────────────────────────────────────────────────┐ │ │ │ svc_register(xprt, prognum, versnum, dispatch, protocol) │ │ SVCXPRT *xprt; │ │ ulong prognum, versnum; │ │ void (*dispatch) (); │ │ ulong protocol; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма привязывает prognum и versnum к отправляющей процедуре dispatch. Если protocol имеет ненулевое значение, отоб- ражение тройки [prognum, versnum, protocol] на xprt->xp_port так- же реализуется с помощью локальной программы portmap (в общем случае protocol имеет значения 0, IPPROTO_UDP или IPPROTO_TCP). Процедура dispatch() имеет следующую форму: ┌────────────────────────────────────────────────────────────┐ │ │ │ dispatch(request, xprt) │ │ struct svc_req *request; │ │ SVCXPRT *xprt; │ │ │ └────────────────────────────────────────────────────────────┘ В случае успешного выполнения код возврата подпрограммы svc_register равен 1, в противном случае - нулю. svc_run() ┌────────────────────────────────────────────────────────────┐ │ │ │ svc_run() │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма ожидает поступления RPC-запросов и вызывает со- ответствующую процедуру их обработки (используя svc_getreq). Для выхода из подпрограммы требуется вызов системной функции select. svc_sendreply() ┌────────────────────────────────────────────────────────────┐ │ │ │ svc_sendreply(xprt, outproc, out) │ │ SVCXPRT *xprt; │ │ xdrproc_t outproc; │ │ char *out; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма вызывается отправляющей процедурой механизма RPC для посылки результатов удаленного запроса. Параметр xprt описы- вает выход на транспортный механизм вызывающего процесса; outproc - XDR-процедура, используемая для кодирования результатов; out - адрес результатов. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. svc_unregister() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ svc_unregister(prognum, versnum) │ │ ulong prognum, versnum; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма отменяет все соответствия между парой [prognum, versnum] и отправляющими процедурами, а также тройкой [prognum, versnum, *] и номером порта. svcerr_auth() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ svcerr_auth(xprt, why) │ │ SVCXPRT *xprt; │ │ enum auth_stat why; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма вызывается отправляющей процедурой в том случае, когда последняя отказывается от выполнения удаленной процедуры вследствие ошибки идентификации. svcerr_decode() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ svcerr_decode(xprt) │ │ SVCXPRT *xprt; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма вызывается отправляющей процедурой в том случае, когда последней не удается декодировать свои параметры. См. также svc_getargs(). svcerr_noproc() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ svcerr_noproc(xprt) │ │ SVCXPRT *xprt; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма вызывается отправляющей процедурой в том случае, когда последней не удается применить номер процедуры, используе- мый в запросе. svcerr_noprog() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ svcerr_noprog(xprt) │ │ SVCXPRT *xprt; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма вызывается в том случае, если нужная программа не была зарегистрирована с пакетом RPC. Разработчики обычно не пользуются этой подпрограммой. svcerr_progvers() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ svcerr_progvers(xprt) │ │ SVCXPRT *xprt; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма вызывается в том случае, если нужная версия программы не была зарегистрирована с пакетом RPC. Разработчики обычно не пользуются этой подпрограммой. svcerr_systemerr() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ svcerr_systemerr(xprt) │ │ SVCXPRT *xprt; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма вызывается отправляющей процедурой в том случае, когда последняя обнаруживает системную ошибку, не учтенную ни в одном из протоколов. Пример: исчерпание ресурсов памяти. svcerr_weakauth() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ svcerr_weakauth(xprt) │ │ SVCXPRT *xprt; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма вызывается отправляющей процедурой в том случае, когда последняя отказывается от выполнения удаленной процедуры вследствие неполноты описания параметров идентификации. Подпрог- рамма обращается к svcerr_auth(xprt, AUTH_TOOWEAK). svcraw_create() ┌────────────────────────────────────────────────────────────┐ │ │ │ SVCXPRT * │ │ svcraw_create() │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма создает модель транспортного механизма RPC с возвращением ему указателя. Этот механизм представляет собой бу- фер в адресном пространстве процесса, так что соответствующий клиент RPC будет работать в том же самом пространстве; см. clntraw_create(). Это позволяет моделировать RPC и собирать нак- ладные расходы RPC (например, общее время передачи) вместе, без какого-либо вмешательства ядра. В случае неудачи код возврата подпрограммы - NULL. svctcp_create() ┌────────────────────────────────────────────────────────────┐ │ │ │ SVCXPRT * │ │ svctcp_create(sock, send_buf_size, recv_buf_size) │ │ int sock; │ │ uint send_buf_size, recv_buf_size; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма создает транспортный механизм RPC, действующий на базе TCP/IP, с передачей ему указателя. Этот механизм ассоции- руется с гнездом sock, при этом если sock имеет значение RPC_ANYSOCK, создается новое гнездо. Если гнездо не связано с ло- кальным портом TCP, оно привязывается к произвольному порту. По завершении xprt->xp_sock имеет значение номера гнезда, а xprt->xp _port - номера порта. В случае неудачи подпрограмма возвращает значение NULL. Пос- кольку RPC на базе TCP использует буферизованный ввод-вывод, пользователи могут указать размеры буферов отправки и приема. svcudp_create() ┌────────────────────────────────────────────────────────────┐ │ │ │ SVCXPRT * │ │ svcudp_create(sock) │ │ int sock; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма создает транспортный механизм RPC, действующий на базе UDP/IP, с передачей ему указателя. Этот механизм ассоции- руется с гнездом sock, при этом если sock имеет значение RPC_ANYSOCK, создается новое гнездо. Если гнездо не связано с ло- кальным портом UDP, оно привязывается к произвольному порту. По завершении xprt->xp_sock имеет значение номера гнезда, а xprt->xp _port - номера порта. В случае неудачи подпрограмма возвращает значение NULL. ----------------------------------------------------------------- Замечание. Поскольку в RPC на базе UDP сообщения могут включать в себя от 4 до 8 Кбайт закодированной информации, этот механизм не может использоваться процедурами, которые имеют громоздкие аргументы или результаты. ----------------------------------------------------------------- xdr_accepted_reply() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_accepted_reply(xdrs, ar) │ │ XDR *xdrs; │ │ struct accepted_reply *ar; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма используется для внешнего описания сообщений RPC. С ее помощью пользователю предоставляется возможность гене- рации RPC-сообщений без использования пакета RPC. xdr_array() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_array(xdrs, arrp, sizep, maxsize, elsize, elproc) │ │ XDR *xdrs; │ │ char **arrp; │ │ uint *sizep, maxsize, elsize; │ │ xdrproc_t elproc; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между массивами и их внешним представлением. Параметр arrp является ад- ресом указателя на массив, sizep - адресом числа элементов в мас- сиве; размерность массива не должна превышать maxsize. Параметр elsize имеет смысл sizeof() для элементов каждого массива, elproc - XDR-фильтр, устанавливающий соответствие между Си-формой предс- тавления элементов в массиве и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_authunix_parms() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_authunix_parms(xdrs, aupp) │ │ XDR *xdrs; │ │ struct authunix_parms *aupp; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма используется для внешнего описания "верительных грамот" в ОС XENIX и UNIX. С ее помощью можно генерировать пос- ледние без использования пакета идентификации RPC. xdr_bool() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_bool(xdrs, bp) │ │ XDR *xdrs; │ │ bool_t *bp; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между бу- левыми переменными и их внешним представлением. При выполнении кодирования фильтр генерирует значение, равное 1 или 0. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_bytes() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_bytes(xdrs, sp, sizep, maxsize) │ │ XDR *xdrs; │ │ char **sp; │ │ uint *sizep, maxsize; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между строками байт заданного размера и их внешним представлением. Па- раметр sp является адресом указателя строки. Длина строки разме- щается по адресу sizep; строки не могут быть длиннее maxsize. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_callhdr() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ xdr_callhdr(xdrs, chdr) │ │ XDR *xdrs; │ │ struct rpc_msg *chdr; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма используется для внешнего описания сообщений RPC. С ее помощью пользователю предоставляется возможность гене- рации RPC-сообщений без использования пакета RPC. xdr_callmsg() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_callmsg(xdrs, cmsg) │ │ XDR *xdrs; │ │ struct rpc_msg *cmsg; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма используется для внешнего описания сообщений RPC. С ее помощью пользователю предоставляется возможность гене- рации RPC-сообщений без использования пакета RPC. xdr_double() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_double(xdrs, dp) │ │ XDR *xdrs; │ │ double *dp; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между числами двойной точности и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_enum() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_enum(xdrs, ep) │ │ XDR *xdrs; │ │ enum_t *ep; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между данными перечислимого типа (enum) (фактически целого) и их внеш- ним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_float() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_float(xdrs, fp) │ │ XDR *xdrs; │ │ float *fp; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между числами с плавающей точкой и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_inline() ┌────────────────────────────────────────────────────────────┐ │ │ │ long * │ │ xdr_inline(xdrs, len) │ │ XDR *xdrs; │ │ int len; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма инициирует выполнение встроенной процедуры, со- ответствующей XDR-потоку xdrs. Возвращает указатель на непрерыв- ный участок буферного пространства потока; len - длина буфера в байтах. Следует заметить, что указатель приводится к типу long *. ----------------------------------------------------------------- Замечание. Код возврата xdr_inline() может иметь значение 0 (NULL) в том случае, если подпрограмма не в состоянии выделить непрерывный участок буферного пространства. ----------------------------------------------------------------- xdr_int() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_int(xdrs, ip) │ │ XDR *xdrs; │ │ int *ip; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между це- лыми числами и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_long() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_long(xdrs, lp) │ │ XDR *xdrs; │ │ long *lp; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между длинными (long) целыми числами и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_opaque() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_opaque(xdrs, cp, cnt) │ │ XDR *xdrs; │ │ char *cp; │ │ uint cnt; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между "непрозрачными" данными фиксированного размера и их внешним представлением. Параметр cp является адресом объекта, cnt - его размер в байтах. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_opaque_auth() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_opaque_auth(xdrs, ap) │ │ XDR *xdrs; │ │ struct opaque_auth *ap; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма используется для внешнего описания сообщений RPC. С ее помощью пользователю предоставляется возможность гене- рации RPC-сообщений без использования пакета RPC. xdr_pmap() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_pmap(xdrs, regs) │ │ XDR *xdrs; │ │ struct pmap *regs; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма используется для внешнего описания параметров различных процедур portmap. С ее помощью можно генерировать эти параметры без использования интерфейса pmap. xdr_pmaplist() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_pmaplist(xdrs, rp) │ │ XDR *xdrs; │ │ struct pmaplist **rp; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма используется для внешнего описания перечня свя- занных с портами соответствий. С ее помощью можно генерировать эти параметры без использования интерфейса pmap. xdr_reference() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_reference(xdrs, pp, size, proc) │ │ XDR *xdrs; │ │ char **pp; │ │ uint size; │ │ xdrproc_t proc; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма обеспечивает представление указателей в структу- рах. Параметр pp является адресом указателя; size - размер струк- туры, на которую указывает *pp; proc - XDR-процедура, проверяющая соответствие между Си-формой структуры и ее внешним представлени- ем. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_rejected_reply() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_rejected_reply(xdrs, rr) │ │ XDR *xdrs; │ │ struct rejected_reply *rr; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма используется для внешнего описания сообщений RPC. С ее помощью пользователю предоставляется возможность гене- рации RPC-сообщений без использования пакета RPC. xdr_replymsg() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_replymsg(xdrs, rmsg) │ │ XDR *xdrs; │ │ struct rpc_msg *rmsg; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма используется для внешнего описания сообщений RPC. С ее помощью пользователю предоставляется возможность гене- рации RPC-сообщений без использования пакета RPC. xdr_short() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_short(xdrs, sp) │ │ XDR *xdrs; │ │ short *sp; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между ко- роткими (short) целыми числами и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_string() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_string(xdrs, sp, maxsize) │ │ XDR *xdrs; │ │ char **sp; │ │ uint maxsize; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между строками (массивами литер) и их внешним представлением. Размер строк не должен превышать maxsize. Обратите внимание на то, что sp является адресом указателя строки. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_u_int() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_u_int(xdrs, up) │ │ XDR *xdrs; │ │ unsigned *up; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между це- лыми без знака и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_u_long() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_u_long(xdrs, ulp) │ │ XDR *xdrs; │ │ unsigned long *ulp; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между длинными целыми без знака и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_void() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_void() │ │ │ └────────────────────────────────────────────────────────────┘ Код возврата подпрограммы всегда равен 1. xdr_wrapstring() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_wrapstring(xdrs, sp) │ │ XDR *xdrs; │ │ char **sp; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма обращается к xdr_string(xdrs,sp,MAXUNSIGNED); где MAXUNSIGNED - максимальное значение целого числа без знака. Может быть полезна, поскольку пакет XDR передает подпрограммам только два параметра, в то время как xdr_string(), одной из наи- более часто используемых процедур, требуется три параметра. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xprt_register() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ xprt_register(xprt) │ │ SVCXPRT *xprt; │ │ │ └────────────────────────────────────────────────────────────┘ После создания выходов на транспортный механизм RPC они долж- ны зарегистрировать себя с сервисным пакетом RPC. Подпрограмма модифицирует значение глобальной переменной svc_fds. Разработчики обычно не используют эту подпрограмму. xprt_unregister() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ xprt_unregister(xprt) │ │ SVCXPRT *xprt; │ │ │ └────────────────────────────────────────────────────────────┘ Перед удалением выхода на транспортный механизм RPC этот вы- ход должен отменить свою ранее выполненную регистрацию с сервисным пакетом RPC. Подпрограмма модифицирует значение гло- бальной переменной svc_fds. Разработчики обычно не используют эту подпрограмму.