3. Вызов удаленных процедур

Средства вызова удаленных процедур (RPC) является составной частью более общего средства, называемого Open Network Computing (ONC), разработанного фирмой Sun Microsystems и получившего всеобщее признание в качестве промышленного стандарта.

ONC помимо RPC включает в себя средства внешнего представления данных (XDR), необходимые для организации обмена информацией в гетерогенных сетях, включающих в себя ЭВМ различной архитектуры, и средства монтирования удаленных файловых систем (NFS), обеспечивающее доступ пользователям локального узла сети к файлам, физически расположенным на удаленных узлах, как к файлам локальным.

Средство RPC реализует модель "клиент-сервер", где роль клиента играет прикладная программа, обращающаяся к набору процедур (функций), исполняемых на удаленном узле в качестве сервера. RPC предоставляет прикладным программистам сервис более высокого уровня, чем ранее рассмотренных два, т.к. обращение за услугой к удаленным процедурам выполняется в привычной для программиста манере вызова "локальной" функции языка программирования СИ. RPC реализовано, как правило, на базе socket-интерфейса и/или TLI. При пересылке данных между узлами сети в RPC для их внешнего представления используется стандарт XDR.

Средство RPC предоставляет программистам сервис трех уровней:

  1. препроцессор rpcgen, преобразующий исходные тексты "монолитных" программ на языке программирования СИ в исходные тексты программы-клиента и программы-сервера по спецификации программиста;
  2. библиотека функций вызова удаленных процедур по их идентификаторам;
  3. библиотека низкоуровневых функций доступа к внутренним механизмам RPC и ниже лежащим протоколам транспортного уровня.

В данном учебном учебном пособии рассматриваются только средства RPC среднего уровня.

Согласно идеологии RPC все процедуры (функции) некоторого распределенного приложения, планируемые к исполнению на одном и том же удаленном узле вычислительной сети, объединяются в единый модуль, оформляемый в виде исполняемого файла и характеризующегося уникальным "номером программы". Допустимо иметь несколько вариантов такого модуля, идентифицируемых уникальным "номером версии". Каждая процедура в составе модуля имеет уникальный "номер процедуры". Таким образом, для однозначной идентификации конкретной процедуры-сервера используется четверка:

Каждая процедура-сервер прежде, чем она станет доступной для обращения к ней, должна быть зарегистрирована на соответствующем узле сети. Регистрация процедуры делает ее известной под соответствующими номерами (программы, версии и, собственно, процедуры) сетевому демону portmapper на локальном узле сети. Удаленный RPC-клиент, обращаясь скрытно от пользователя к этому демону, может получить точный сетевой адрес процедуры, который и будет использовать в дальнейшем для прямых обращений к процедуре-серверу.

Процедура-сервер, создаваемая средствами RPC любого уровня, должна иметь единственный аргумент и единственный результат. Это ограничение заставляет прикладного программиста в случае необходимости передачи в процедуру (или возврата из нее) нескольких аргументов (результатов) компоновать их в сложные агрегаты данных (структуры, массивы, списки и т.п.).

Для создания распределенных приложений средствами RPC среднего уровня достаточно использовать три функции: registerrpc, svc_run (на стороне сервера) и callrpc (на стороне клиента).

Примечание. Столь малое количество функций объясняется тем, что средний уровень средств RPC беден с точки зрения возможностей выбора используемого транспорта данных, управления количеством ретрансляций данных, назначения тайм-аутов, организации асинхронной обработки и т.п.

Программисты распределенных приложений кроме собственно функций RPC обязаны также использовать функции преобразования данных во внешнее представление согласно стандарту XDR (так называемые XDR-функции).

3.1 Регистрации процедуры-сервера

Регистрация процедуры в качестве сервера на узле сети выполняется функцией registerrpc, имеющей следующий вид

#include <sys/types.h> #include <rpc/rpc.h> int registerrpc (prognum, vernum, procnum, procname, inproc, outproc) u_long prognum; u_long vernum; u_long procnum; char *(*procname) (); xdrproc_t inproc; xdrproc_t outproc;

Аргументы prognum, vernum и procnum задают номера программы, версии и процедуры соответственно. Номера версии и процедуры назначаются программистом произвольно. Номер же программы, находящейся в стадии разработки, должен назначаться из диапазона 0x20000000...0x3fffffff.

Аргумент procname задает функцию языка программирования СИ, регистрируемую в качестве сервера. Эта функция (процедура) вызывается с указателем на ее аргумент и должна возвращать указатель на свой результат, располагаемый в статической или динамически выделенной (функциями malloc или calloc) памяти. Для хранения результата нельзя использовать автоматически выделяемую память (напоминаем, что локальные переменные функций располагаются именно в такой памяти).

Аргументы inproc и outproc задают XDR-функции преобразования, соответственно, аргумента и ее результата.

При успешном выполнении функция registerrpc возвращает 0, иначе - число "-1".

3.2. Диспетчеризация запросов к процедурам-серверам

Для приема запросов к процедурам-серверам от клиентов и диспетчеризации их используется функция svc_run, имеющая следующий вид

#include <rpc/rpc.h> void svc_run ();

Не имеющая аргументов функция svc_run должна вызываться после регистрации всех диспетчируемых ею процедур-серверов. При успешном выполнении svc_run никогда не возвращает управление в вызвавшую ее программу.

3.3. Запрос к процедуре-серверу

Для запроса к удаленной процедуре-серверу из программы-клиента используется функция callrpc, имеющая следующий вид

#include <sys/types.h> #include <rpc/rpc.h> int callrpc (host, prognum, vernum, procnum, inproc, in, outproc, out) char *host; u_long prognum; u_long vernum; u_long procnum; xdrproc_t inproc; char *in; xdrproc_t outproc; char *out;

Аргумент host задает имя узла, на котором функционирует вызы- ваемая процедура-сервер.

Аргументы prognum, vernum и procnum задают номера программы, версии и, собственно, вызываемой процедуры-сервера. К моменту вызова процедуры она должна быть зарегистрирована на узле сети, определяемом аргументом host.

Аргумент in должен указывать на данные, передаваемые процеду- ре-серверу в качестве аргумента.

Аргумент out должен указывать на область памяти, предназначенную для размещения в ней результата работы процедуры-сервера.

Аргументы inproc и outproc задают XDR-функции преобразования, соответственно, аргумента процедуры-сервера и ее результата.

На время обработки процедурой-сервером запроса к ней программа-клиент переходит в состояние ожидания результата.

При успешном выполнении вызова удаленной процедуры-сервера функция registerrpc возвращает 0, иначе - число "-1".

3.4. XDR-функции

Для преобразования данных в/из XDR-формат библиотека функций RPC содержит ряд функций, некоторые из них перечислены ниже:

В ситуациях, когда в процедуру-сервер аргумент не передается (или от нее не возвращается результат), используется функция-"заглушка" xdr_void.

XDR-функции универсальны: в зависимости от контекста их использования они могут преобразовывать данные из внутреннего представления, специфичного для ЭВМ данной архитектуры, во внешнее согласно стандарту XDR и наоборот, а также динамически выделять/освобождать память под сложные агрегаты данных.

При необходимости прикладной программист может легко расширить имеющийся набор XDR-функций, создав свои собственные на базе уже имеющихся.

3.5. Пример использования средств RPC

В данном разделе рассматривается использование средств RPC на очень простом примере взаимодействия программы-клиента с одной удаленной процедурой-сервером.

Содержательная часть программ примитивна:

  1. процедура-сервер, получив в качестве аргумента запроса целое число, возвращает клиенту строку символов "even" ("четное") или "odd" ("нечетное");
  2. программа-клиент обращается к процедуре-серверу, передавая ей целое число, являющееся результатом подсчета количества символов в некоторой строке, и выводит в стандартный вывод ответ процедуры-сервера.

3.5.1. Включаемый файл

При разработке прикладных программ средствами RPC целесообразно для номеров программ, версий и процедур определять символические имена в едином файле, включаемом в исходный текст как процедур-серверов, так и клиентов. В нашем примере таким файлом будет файл common.h, имеющий следующий вид

#define MY_PROG (u_long) 0x20000001 #define MY_VER (u_long) 1 #define MY_PROC1 (u_long) 1

3.5.2. Программа-сервер

Текст программы-сервера на языке программирования СИ выглядит следующим образом

1 #include <rpc/rpc.h> 2 #include <stdio.h> 3 #include "common.h" 4 char **proc1 (); 5 main () 6 { 7 registerrpc (MY_PROG, MY_VER, MY_PROC1, proc1, xdr_int, xdr_wrapstring); 8 svc_run(); 9 fprintf (stderr, "Error: svc_run returned\n"); 10 exit (1); 11 } 12 char **proc1 (indata_p) 13 int *indata_p; 14 { 15 static char *res; 16 static char even[] = {"even"}; 17 static char odd[] = {"odd"}; 18 printf ("Number recieved is %d\n", *indata_p); 19 if (*indata_p % 2) { 20 res = odd; } 21 else { 22 res = even; }; 23 return &res; 24 }

Строки 1...3 описывают включаемые файлы, содержащие определения для всех необходимых структур данных и символических констант.

Строка 4 объявляет тип функции proc1. Это объявление необходимо, поскольку в нашей программе функция proc1 используется (в строке 7) раньше по тексту, чем определяется (в строке 12).

В строке 7 функция proc1 регистрируется в качестве процедуры-сервера, имеющей аргумент в виде целого числа и результат в виде строки символов. Таким образом можно зарегистрировать произвольное количество процедур-серверов с одинаковыми номерами программы и версии.

В строке 8 программа посредством обращения к функции svc_run переходит в состояние ожидания запросов клиентов. Нормально функционирующая программа никогда к выполнению следующих за вызовом svc_run операторов не переходит.

В строках 9 и 10 выводится сообщение об ошибке и завершается выполнение программы с кодом ошибки 1.

Строки 12...24 содержат описание функции proc1. Необходимо еще раз подчеркнуть, что функция работает с указателями на ее аргумент и результат.

Переменные res, even и odd объявлены статическими, чтобы они сохранялись в памяти программы и после выхода из функции proc1.

3.5.3. Программа-клиент

Текст программы-клиента на языке программирования СИ выглядит следующим образом

1 #include <rpc/rpc.h> 2 #include <stdio.h> 3 #include "common.h" 4 main (argc, argv) 5 int argc; 6 char **argv; 7 { 8 int arg; 9 char *answer; 10 int stat; 11 if (argc < 3) exit (1); 12 arg = strlen (argv[2]); 13 if (stat = callrpc (argv[1], MY_PROG, MY_VER, MY_PROC1, xdr_int, &arg, xdr_wrapstring, &answer) != 0) { 14 clnt_perrno (stat); 15 exit (2); 16 }; 17 printf ("Number of letters in %s is %s\n", argv[2], answer); 18 exit (0); 19 }

В строке 11 проверяется количество аргументов командной строки, с помощью которой программа-клиент вызывается на выполнение (аргументов не должно быть меньше двух).

В строке 12 переменной целого типа arg присваивается число, равное количеству символов второго аргумента командной строки.

В строке 13 вызывается удаленная процедура-сервер, резидентная на узле сети, имя которого задается первым аргументом командной строки. Процедуре передается указатель на ее аргумент (целое число), сама же она возвращает также указатель на свой результат (при этом память под возвращаемую строку символов нужного размера автоматически выделяется XDR-функцией xdr_wrapstring).

Строка 14 содержит обращение к функции clnt_perrno, которая выводит в stderr текст сообщения, характеризующего ошибку, которая по разным причинам может возникнуть при выполнении вызова удаленной процедуры.

Строка 17 выводит в stdout сообщение, содержащее ответ удаленной процедуры-сервера.