Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ВЗАИМОДЕЙСТВИЕ СЕТЕВЫХ ПРИЛОЖЕНИЙ.doc
Скачиваний:
53
Добавлен:
01.05.2014
Размер:
579.07 Кб
Скачать

1.4 Задания к лабораторной работе 1

Задание 1. Написать две программы, которые работают на разных машинах и обмениваются сообщениями через дейтаграммные сокеты. Первая программа посылает второй сообщение, а затем ждёт от неё ответа. Вторая программа принимает сообщение, выводит ею на экран, а затем посылает ответ первой программе. Если обе программы не получают сообщение в течение определенного времени, то они заканчивают работу с уведомлением о непринятии сообщения. При запуске нерпой программы указываются два параметра: имя машины, на которой работает вторая программа, и номер ее порта. Для второй программы задается только один параметр – номер – порта, в который она принимает сообщение.

Задание 2. Выполнить задание 1, но для обмена сообщениями использовать потоковый сокет. Первая программа должна перед посылкой сообщения через каждые 10 с сделать несколько попыток установить соединение. В случае неудачи программа завершается с уведомлением о невозможности образовать канал связи.

Задание 3. Написать серверную и клиентскую программы. Клиентская программа генерирует случайную последовательность из 10 чисел, выводит ее на экран, а затем отсылает серверу . Сервер и течение 60 с ждет запросов от клиентов и в случае их отсутствия завершает работу. При поступлений запроса сервер порождает обслуживающий процесс, который принимает числа, выводит их на экран, упорядочивает и отсылает обратно клиенту. Клиент полученную от сервера последовательность также выводит на экран. Клиентская программа должна быть запущена на нескольких машинах. При ее запуске указывается порт обмена и имя машины, на которой работает серверная программа.

Лабораторная работа 2 построение серверных и клиентских программ с использованием компилятора rpcgen

Цепь работы: знакомство с технологией создания удалённых процедур с помощью компилятора rpcgen.

2.1. Описание интерфейса удаленной процедуры

Разработка удаленной процедуры должна начинаться с описания ее интерфейса. Такое описание выполняется на специальном языке и помещается в файл с расширением “х”. В этом файле указывается регистрационные параметры удаленной процедуры: номер программного модуля, где будет реализована процедура, номер процедуры и номер ее версии; а также определяются параметры

-18-

вызова: имя процедуры, типы входных аргументов и возвращаемого значения. Обычно несколько функционально сходных процедур помешаются в программный модуль, причем каждая такая процедура может иметь несколько версий. Поэтому описание интерфейсов, входящих в один программный модуль, разбивается на секции. В каждой секции описываются параметры вызовов всех процедур, принадлежащих одной версии. Номера всех пользовательских процедур и их версий должны начинаться с единицы. Если существуют несколько версий одной процедуры, то описание вызова для каждой версии должно быть включено в соответствующую секцию, при этом имя процедуры и ее номер остаются без изменений.

В начале описания интерфейса может присутствовать С- код. Этот код включается в серверную и клиентскую программы и состоит из определений типов и констант, которые используются при задании типов возвращаемого значения и аргументов процедуры. Для определения типов можно использовать препроцессорную директиву #include, но библиотечные файлы заголовков не рекомендуется включать в описание интерфейса. Это связано с тем, что библиотечные файлы заголовков могут содержать конструкции, непонятные компилятору rpcgen. Если при описании прототипа процедуры нужно использовать библиотечный тип данных, то его нужно переопределить в С-коде интерфейса. Шаблон описания интерфейса выглядит следующим образом:

programИМЯ МАКРОСА, ОПРЕДЕЛЯЮЩЕЕ НОМЕР ПРОГРАММНОГО

МОДУЛЯ”

{

version "ИМЯ МАКРОСА, ОПРЕДЕЛЯЮЩЕЕ НОМЕР ВЕРСИИ

ПРОЦЕДУР”

{

“тип возвращаемого значения” “ИМЯ ПРОЦЕДУРЫ” (“типы аргументов”)

= “номер процедуры”;

}= “номер версии процедуры”;

} = “номер программы”;

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

-19-

символьного указателя char* при описании параметров вызова процедуры. Такой указатель может ссылаться как на строку символов, заканчивающуюся NULL- символом, так и на любой байт памяти. Если тип возвращаемого значения или аргумента является указателем на строку символов, то в описании прототипа этот тип должен быть задан как string.

Все процедуры, собранные в одном программном модуле, вызываются по номеру модуля и номеру версии процедуры. После запуска такой модуль становится сервером для этих процедур. Номера серверов, выполняющихся ни одной машине, должны быть уникальны, обычно эти номера распределяет администратор. Номера с 1 до 0х10000000 зарезервированы для системных серверов. Поэтому рекомендуется в лабораторных работах серверным программам присваивать номера больше, чем 0x10000000. Максимальные значения номеров модуля, версии и процедуры не должны превышать максимальное значение типа unsigned long int. Для 32-разрядных машин это значение равно 0хffffffff.

Пример описания интерфейса удаленной процедуры:

program CURRENT_TIMEPROG

{

version CURRENT_TIMEVERS

{

string CURRENT_TIME()=l;

}=1;

}=0x33330000;

В этом примере описывается интерфейс удаленной процедуры CURRENT_TIME. Ей присвоен номер 1 и версия 1. Она будет располагаться в программном модуле с номером 0x33330000. Для обозначения номера модуля и номера версии процедуры в клиентской и серверной программах в описании интерфейса определены макросы CURRENT_TIMEPROG и CURRТЕNT_TIMEVERS. Процедура не имеет параметров и возвращает значение типа string. Она предназначена для получения от сервера значений текущего местного времени и даты. Это описание интерфейса помещается в файл current_time.x .

2.2. Построение заглушек для удаленной процедуры

Основное назначение компилятора rpcgen - упростить программирование протоколов связи между клиентской и серверной программами. Компилятор генерирует два специальных модуля, называемых заглушкой клиента и заглушкой сервера, которые освобождают пользователей и разработчика процедуры от всех действий по преобразованию данных и передаче их по сети. Клиент выполняет

-20-

локальной визой своей заглушки, передает ей параметры, а она возвращает результат. В этом случае вызов удаленной процедуры остается таким же, как вызов локальной функции. Заглушка клиента упаковывает параметры и формирует сетевой запрос к серверу. На сервере запрос oт клиента принимает заглушка сервера. Она в свою очередь осуществляет запуск процедуры, передавая ей полученные по сети параметры. После выполнения процедуры заглушка сервера передает клиенту возвращенные процедурой значения.

Построение заглушек выполняет компилятор rpcgen. Он их строит по описанию интерфейса удаленной процедуры, содержащемуся в файле с расширением “х”. Формат вызова компилятора следующий:

rpcgen[“ключи”] “имя.х

При вызове компилятора можно задать различные ключи. Полное их описание можно прочитать на компьютере с помощью команды man rpсgen. Наиболее часто используются ключи -I, -К “время” и -s “протокол”.

Серверный модуль можно настроить на два режима работы: на режим однократного запуска и на режим запуска по запросу клиента. Первый режим предполагает постоянное нахождение серверного процесса в памяти машины. Серверный процесс запускается только один раз, после чего он переходит в состояние ожидания поступления запроса от клиента. Когда приходит запрос от клиента, сервер обслужит его и снова перейдет в состояние ожидания. Такой Серверный процесс можно уничтожим, только при помощи команды kill.

Режим запуска по запросу клиента задается ключом -I. В этом режиме серверный модуль запускается каждый раз, когда приходит запрос от клиента. Обслужив клиента, серверный процесс либо завершается сразу, либо по истечении некоторого момента времени. Это время определяет ключ -К "время". Параметр "время" задает время ожидания серверной программой следующего клиентского запроса. Если клиент за это время не обратился ни к одной из удаленных процедур, то серверная программа завершается. По умолчанию время ожидания равно 120 с. Если укачан ключ -К 0, то серверная программа завершаемся сразу после обслуживания клиента. При значении -К -1 она не завершится, а перейдет в состояние ожидания и будет находиться в нем до прихода следующею запроса.

Режим многократного запуска требует выполнения специальных действий администратором машины. Когда серверный модуль устанавливается на машине, администратор должен добавить записи в системные файлы /etc/rpc и /etc/inetd.conf. Файл /etc/rpc является вспомогательным, в нем задается соответствие между именем модуля и его номером. Затем его имя используется в файле /etc/inetd.conf для обозначения серверного модуля. В файле /etc/inetd.conf также указывается номера версий процедур, входящих в этот модуль, тип про-

-21-

токола для связи с клиентом, каталог, где располагается модуль. Список всех серверов, включенных в систему и работающих как в режиме однократного, так и многократного запуска, можно вынести на экран с помощью утилиты rpcinfo-p.

Ключ -s "протокол" задает тип протокола связи между сервером и клиентом. Для связи может использоваться транспортный протокол с установкой соединения или без установки соединения. В первом случае значение ключа должно быть tcp, во втором - udp. По умолчанию компилятор генерирует заглушку сервера, обеспечивающую взаимодействие клиента и сервера через сокеты как по протоколу udp, так и по протоколу tcp. Протокол обмена выбирает клиент при обращении к удаленной процедуре.

Пример вызова компилятора:

rpcgen current_time.x

Компилятор rpcgen пo описанию интерфейса удаленной процедуры, содержащемуся в файле имя.х, генерирует три файла: файл заголовка для клиентской и серверной программ имя.h, файл заглушки для клиента имя_clnt.c и файл заглушки для сервера имя_svc.c. Например, для файла current_time.x будут построены файлы current _time.h, current_time_clnt.c и current_time _svc.c.

В файле заголовка имя.h определяются значения макросов номера программы, номера версии и номера процедуры, а также содержатся С-код и объявления прототипов вызова заглушки клиента и серверной функции. Компилятор rpcgen генерирует прототипы для трех версий языка С: C++, стандарт С и ранняя версия С. Большинство трансляторов языка С, в том числе gcc, поддерживают стандарт С. Поэтому при написаний программы клиента и серверной функции рекомендуется использовать прототипы, сгенерированные под стандарт С. В этих прототипах имена, тип возвращаемою значения и типы аргументов отличаются от имени макроса и типов удаленной процедуры, указанных в файле описания интерфейса.

Имя н типы аргументов в прототипе вызова заглушки клиента получаются следующим образом: в имени макроса процедуры заменяются прописные символы на строчные и к нему через знак подчеркивания присоединяется номер версий процедуры, а к типам возвращаемого значения и аргументам добавляется символ *. Это означает, что аргументы и возвращаемое значение будут передаваться по адресу. Кроме того, в список аргументов прототипа добавляется указатель на объект типа CLIENT, который используется для организации связи с сервером.

Имя серверной функции также получается из имени макроса процедуры путем замены прописных символов на строчные, но к нему через знак подчеркивания присоединяются сначала номер версии, а затем суффикс svc. К типам аргументов и возвращаемого значения прототипа серверной функции

-22-

добавляется символ *. Список аргументов функции также содержит дополнительный параметр - указатель на структуру типа struct svc_req*, в которой будет содержаться информация о клиентском запросе. Состав полей этой структуры определен в файле заголовка <rpc/svc.h>.Процесс генерации прототипов вызовов заглушки клиента и серверной функции для стандарта языка С можно проиллюстрировать на следующем примере. Если в описании интерфейса удаленная процедура была объявлена как

version MSGRPCVERS

{

int MSGRPC(string)=l;

};

то прототип вызова заглушки клиента будет иметь вид (int* msgrpc_2 (char**,CLIENT*), а прототип вызова серверной функции int* msgrpc_2_svc(char**, struct svc req*).

Полный текст файла заголовка current_time.h, построенного компилятором rpcgen, для приведенного ранее описания интерфейса удаленной процедуры CURRENT_TIME будет таким:

#ifndef_CURRNT_TIME_H_RPCGEN

#define_CURRENT_TIME_H_RPCGEN

#include <rpc/rpc.h>

#define CURRENT_TIMEPROG ((u_long)0x33330000)

#define CURRENT_TIMEVERS ((u_long)l)

#ifdef _ cplusplus

#define CURRENT_TIME ((u_long)l)

extern "C" char ** current_time_1(void *, CLIENT *);

exterh "C" char ** current_time_l_svc(void *, struct svc_req *);

/* протокол для стандарта языка С */

#elif_STDC_

#define CURRENT_TIME ((u_long)1)

extern char ** current_time_1(void *, CLIENT *);

extern char ** current_time_l_svc(void *, struct svc_req *);

#else/*Old Style C*/

#define CURRENT_TIME((u_long)l)

-23-

extern char ** current_time_1();

extern char ** current_time_1_svc();

#endif /* Old Style С */

#endif /* I_CURRENT_TIME_H_RPCGEN*/

В файлах заглушек для клиента имя_clnt.c и для сервера имя_svc.c содержится код, обеспечивающий передачу и прием вызова удалённой процедуры по сети. Задачами заглушек являются: прием аргументов, прямое и обратное преобразования их в специальный формат XDR, формирование сетевого запроса но указанному протоколу. Таким образом, заглушки отвечают за информирование и передачу сообщений между программой клиента и удаленной процедурой, освобождая разработчика процедуры и ее пользователей от написания протоколов обмена по сети. Заглушка для клиента должна компилироваться вместе с его программой, а заглушка для сервера - вместе с удаленной процедурой

Если в описании интерфейса присутствуют объявления нестандартных типов данных, то компилятор rpcgen генерирует дополнительный файл имя_xdr.c. В этом файле содержатся прямые и обратные функции XDR - преобразования для значений нестандартных типов данных. При каждой операции обмена между клиентом и сервером XDR -функции будут вызываться дважды: перед посылкой данных по сети и после их приема. Эти функции сначала преобразуют данные в XDR формат, а затем их из XDR-формата в то представление, которое используется программами. Файл имя_хdr.c должен компилироваться вместе с программой клиента и удаленной процедурой.

2.3. Разработка серверной функции

Серверная функция создается пользователем на основании прототипа, описанного в файле имя.h. Она имеем две особенности:

  • значения аргументов и возвращаемое значение являются указателями;

  • результат выполнения функции необходимо сохранить после се

завершения.

Вторая особенность связана с тем, что передача клиенту возвращаемого значения осуществляется заглушкой сервера по окончании работы серверной функции. Поэтому все переменные, связанные с хранением результата, должны размешаться в статической или динамической памяти, т. е. должны быть объявлены либо как static, либо порождены с помощью операторов malloc или new. Если для храпения результатов используется динамическая память, то ее необходимо освобождать, иначе при большом количестве запросов к серверу она будет быстро исчерпана и сервер перестанет работать. Освобождать надо ту динамическую память, которая была выделена на предыдущем вызове функции.

-24-

была выделена на предыдущем вызове функции. Поэтому указатель на освобождаемую память должен быть объявлен как static, а операция освобождения памяти должна быть выполнена в начале серверной функции. Для освобождения памяти можно воспользоваться функцией xdr_free(xdrproc_t, char*). В первом аргументе этой функции указывается имя процедуры, которая должна освобождать память, а во втором - адрес переменной, где хранится возвращаемое значение.

Пример описания серверной функции для процедуры CURRENT_TIME:

/* заголовочные файлы для серверной функции */

#include <time.h>

#include “current_time.h”

#include <siring.h>

/*описание серверной функции */

char** current_time_1_svc(void* a1,struet svc_req*а2)/*заголовок функции*/

{

static char time local[40], char* ptr; /* статические массив и указатель

для хранения результатов */ time_t timesec;

timesec=time(0);

strcpy(time_local, ctime(&timesec)); ptr=(char*)time_local;

return (char**)&ptr; /* возвращаем адрес указателя */

}

Это определение помешается в файл current_time.c. Затем можно с помощью компилятора gcc получить загрузочный модуль серверной программы. Он будет состоять из двух частей: описания серверной функции (файл currcnt_time.c) и заглушки сервера (файл current_ime _svc.c ). Вызов компилятора для построения серверного модуля с процедурой CURRFNT_TIME будет таким:

gcc -о rpc_current_time current_time.c current_time_svc.c

Запуск серверного модуля осуществляется по команде rpc_current__time. После его запуска управление передается заглушке сервера, которая открывает два сокета для взаимодействия с клиентом по протоколу udp и tcp и регистрирует серверную функцию. Номера портов для взаимодействия с клиентом будут назначены ОС. Затем будет вызнан системный RPC-сервер (portmap), который отслеживает запросы клиентов. Если запросов нет, то программа переходит в состояние ожидания. Как только поступит запрос от клиента, RPC-сервер по номеру серверной программы, по номеру версии и имени удаленной процедуры

-25-

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

2.4. Вызов удаленной процедуры в программе клиента

В клиентской программе вызову удаленной процедуры должны предшествовать объявление указателя на объект типа CLIENT и обращение к системной функции сlnt_create. Эта функция объявлена в файле rpc/rpc.h и имеет следующее описание:

CLIENT* clnt_create(const char* hostname, const u_long prognum, const u_long versnum, const char* nettype),

где параметр hostname - задает имя машины, на которой выполняется удаленная процедура; prognum - номер программы, в которой находится процедура; versnum - номер версии удаленной процедуры; nettype - тип протокола для связи с серверной машиной. Значение параметра nettype может быть "tcp" или "udp". Этот параметр должен соответствовать типу протокола, который использует серверная заглушка для взаимодействия с клиентом. Напомним, что тип протокола для серверной заглушки указывает ключ -s при вызове компилятора rpcgen, а если он не указан, то клиент может использовать оба протокола.

Функция clnt_create выполняет соединение между клиентской и серверной машинами по протоколу, указанному в параметре nettype. При неудачном завершении функция clnt_create возвращает NULL, а в случае успеха - указатель CLIENT*, который используется в качестве последнего параметра при вызове клиентской заглушки удаленной процедуры. Проанализировать результат выполнения соединения с сервером можно с помощью системной функции clnt_pcreateerror(char* text). Она выводит на стандартное устройство сообщение, предваряя его строкой, указанной в параметре text.

После обращения к функции clnt_create можно выполнить вызов заглушки клиента, передав, ей параметры для серверной функции и указатель CLIENT*. Если серверная функция возвращает значение NULL, то это означает, что функция выполнена неуспешно. В этом случае вызывается функция clnt_perror(char* text), которая сообщит причину неудачи, предваряя сообщение строкой, указанной как аргумент функции.

В приведенном ниже примере представлена клиентская программа, содержащая обращение к удаленной процедуре CURRENT_TIME по протоколу udp. Аргументом командной строки для этой программы является имя машины, где запущена удаленная процедура.

/* программа клиента содержится в файле cr_client.c */

#include <stdio.h>

-26-

#include “current_time.h”