Пусть приложение работает на отдельном компьютере, и его необходимо
преобразовать, чтобы использовать в "распределенной"
сети. Ниже показано пошаговое преобразование программы, которая выводит
сообщения на терминал.
Однопроцессная версия printmesg.c (рис. 38):
/* printmsg.c: выводит сообщение на терминал */
#include <stdio.h>
main(int argc, char *argv[])
{
char *message;
if (argc != 2) {
fprintf(stderr, "usage: %s <message>\n",argv[0]);
exit(1);
}
message = argv[1];
if (!printmessage(message)) {
fprintf(stderr,"%s: невозможно вывести сообщение\n",argv[0]);
exit(1);
}
printf("Сообщение выведено!\n");
exit(0);
}
/* Вывод сообщения на терминал.
* Возвращает логическое значение, показывающее
* выведено ли сообщение. */
printmessage(char *msg)
{
FILE *f;
f = fopen("/dev/console", "w");
if (f == (FILE *)NULL) {
return (0);
}
fprintf(f, "%s\n", msg);
fclose(f);
return(1);
}
Рис. 38. Однопроцессная версия printmesg.c
Если функцию printmessage() превратить в удаленную процедуру,
ее можно вызывать на любой машине сети.
Сначала необходимо определить типы данных всех аргументов вызова процедуры
и результата. Аргумент вызова printmessage() - строка, а
результат - целое число. Теперь можно написать спецификацию протокола
на языке RPC, который будет описывать удаленную версию printmessage().
Исходный код RPC для данной спецификации:
/* msg.x: Удаленный протокол вывода сообщения */
program MESSAGEPROG {
version PRINTMESSAGEVERS {
int PRINTMESSAGE(string) = 1;
} = 1;
} = 0x20000001;
Удаленные процедуры всегда объявляются как часть удаленных программ.
Код выше описывает полную удаленную программу, которая содержит единственную
процедуру PRINTMESSAGE.
В этом примере, PRINTMESSAGE - это процедура номер 1 в версии
1 удаленной программы MESSAGEPROG с номером программы 0x20000001.
Номера версии увеличиваются, если в удаленной программе изменяются
функциональные возможности. При этом могут быть заменены существующие
процедуры или добавлены новые. Может быть определена более чем одна
версия удаленной программы, а также версия может иметь более одной
определенной процедуры.
Необходимо разработать еще две дополнительные программы. Одной из
них является сама удаленная процедура. Версия printmsg.c
для RPC (рис. 39):
int * printmessage_1(char **msg, struct svc_req *req)
{
static int result; /* должен быть static! */
FILE *f;
f = fopen("/dev/console", "w");
if (f == (FILE *)NULL) {
result = 0;
return (&result);
}
fprintf(f, "%s\n", *msg);
fclose(f);
result = 1;
return (&result);
}
Рис. 39. Версия printmsg.c для RPC.
При этом определение удаленной процедуры printmessage_1
отличается от локальной процедуры printmessage в следующих
моментах:
Она требует указатель на массив символов вместо непосредственного
указателя. Это справедливо для всех удаленных процедур, когда не используется
опция '-N': они всегда используют указатели на аргументы,
а не сами аргументы. Без опции '-N', удаленные процедуры
всегда вызываются с единственным аргументом. Если требуется использовать
более одного аргумента, то аргументы нужно передать в составе структуры.
Она вызывается с двумя аргументами. Второй аргумент содержит информацию
о контексте обращения: номера программы, версии и процедуры, указатель
структуры SVCXPRT (структура SVCXPRT содержат транспортную
информацию). Эта информация становится доступной в случае, если вызываемая
процедура требует ее, чтобы исполнить запрос.
Она возвращает указатель на целое число вместо самого целого числа.
Это также справедливо для удаленных процедур, когда не используется
опция '-N': они возвращают указатели на результат. Результат
должен быть объявлен статическим, если не используются варианты '-М'
(многопоточный) или '-A' (Автоматический режим). Обычно,
если результат объявлен локальным для удаленной процедуре, ссылки
на него серверной частью недействительны после возвращения из удаленной
процедуры. В случае вариантов '-М' и '-A', указатель
на результат передается как третий аргумент процедуру, так что результат
не объявляется в процедуре.
_1 добавляется в конец названия процедуры. В общем случае все вызовы
удаленных процедур, сгенерированные rpcgen, называют следующим образом:
имя процедуры в определении программы (здесь PRINTMESSAGE)
преобразуется к строчным буквам, в конец имени добавляется подчеркивание
(_) вместе с номером версии (здесь 1). Эта схема обозначения позволяет
поддерживать множественные версии той же самой процедуры.
Пример клиентской программы, которая вызывает процедуру (рис. 40):
fprintf(stderr, "%s: невозможно вывести сообщение\n",argv[0]);
exit(1);
}
/* Сообщение выведено на терминал сервера
*/
printf("Сообщение доставлено %s\n", server);
clnt_destroy( clnt );
exit(0);
}
Рис. 40. Клиент для обращения к printmsg.c
Следует отметить следующие особенности клиентской программы вызова
printmsg.c:
Сначала процедурой библиотеки RPC clnt_create() создается
обработчик клиента. Этот обработчик клиента передается процедуре клиента,
которая вызывает удаленную процедуру. Если с помощью обработчика клиента
больше не будут выполняться запросы, он удаляется вызовом clnt_destroy(),
чтобы сохранить ресурсы системы.
Последний параметр clnt_create() определяет, что может использоваться
любой транспорт, отмеченный как видимый (visible) в /etc/netconfig.
Удаленная процедура printmessage_1 именуется тем же самым
способом, как она объявлена в msg_proc.c, за исключением
добавленного обработчика клиента на месте второго аргумента. Она также
возвращает указатель на результат вместо результата.
Вызов удаленной процедуры может завершиться неудачно двумя способами.
Либо произойдет ошибка в механизме RPC, либо может быть ошибка в выполнении
удаленной процедуры. В первом случае удаленная процедура printmessage_1
возвращает NULL. Во втором случае сообщение об ошибке зависит
от приложения. Здесь ошибка возвращается через *result.
Для компиляции примера удаленного rprintmsg:
Откомпилируйте протокол, определенный в msg.x: rpcgen
msg.x. При этом будут созданы заголовочный файл (msg.h),
клиентская часть (msg_clnt.c), и серверная часть (msg_svc.c).
Откомпилируйте исполняемый файл клиента:
cc rprintmsg.c msg_clnt.c -o rprintmsg -lnsl
Откомпилируйте исполняемый файл сервера:
cc msg_proc.c msg_svc.c -o msg_server -lnsl
Объектные файлы C должны быть скомпонованы с библиотекой libnsl,
которая содержит все сетевые функции, включая версии для RPC и XDR.
В этом примере не были созданы никакие процедуры XDR, потому что приложение
использует только основные типы, которые включены в libnsl.
Теперь нужно рассмотреть, что создает rpcgen на основе входного
файла msg.x:
Во-первых, был создан заголовочный файл, названный msg.h,
который содержит директивы #define для MESSAGEPROG,
MESSAGEVERS, и PRINTMESSAGE, чтобы использовать
их в других модулях. Этот файл должен быть включен в модули для клиента
и сервера.
Были созданы процедуры клиента в файле msg_clnt.c. В нем
содержится только одна процедура printmessage_1, которая
вызывается из программы клиента rprintmsg. Если имя входящего
файла для rpcgen - prog.x, то файл результата клиентской
части называется prog_clnt.c.
Была создана программа сервера в файле msg_svc.c, которая
вызывает функцию printmessage_1 из msg_proc.c.
Для входного файла, названного prog.x, файл результата для
сервера называется prog_svc.c.
После того, как программа сервера создана, ее можно установить на
удаленной машине и запустить. (Если машины гомогенны, двоичные файлы
сервера можно просто скопировать. Если они различны, исходные файлы
сервера должны быть компилироваться на удаленной машине отдельно.)