Партнерка на США и Канаду по недвижимости, выплаты в крипто
- 30% recurring commission
- Выплаты в USDT
- Вывод каждую неделю
- Комиссия до 5 лет за каждого referral
Построение приложения RPC
Разработка приложения
Для создания клиент-серверного приложения с использованием Microsoft RPC необходимо:
1) разработать интерфейс;
2) разработать сервер, который реализует этот интерфейс;
3) разработать клиент, который использует этот интерфейс.
Данный рисунок иллюстрирует эти шаги:

Довольно распространенной практикой является параллельная разработка как клиентского, так и серверного приложения. Однако, так как и клиент и сервер зависят от интерфейса, то сам интерфейс необходимо разработать заранее, как видно из рисунка.
Разрабатываем интерфейс
Интерфейс RPC описывает функции, которые реализует серверное приложение. Интерфейс гарантирует, что клиент и сервер взаимодействуют на основании одних и тех же правил, когда клиент вызывает удаленную процедуру, которую предлагает сервер. Интерфейс состоит из названия интерфейса, некоторых атрибутов, необязательных определений типов и констант, и набора объявлений процедур. Каждое объявление процедуры включает имя процедуры, возвращаемый тип и список параметров.
Интерфейсы определяются с использованием MIDL(Microsoft Interface Definition Language = Языка Определения Интерфейсов Microsoft). Если вы знакомы с C или C++, то определение интерфейсов MIDL вам покажутся весьма простыми. MIDL во многом напоминает C и С++.
Определение интерфейса содержится в текстовом файле с расширением .idl. Компилятор MIDL генерирует файл заголовка, который ваша программа будет использовать в исходных кодах клиентского и серверного приложения. Компилятор MIDL также создает два файла на языке C. Один из них будет компилироваться и связываться (link) с клиентской программой, а другой - с серверной. Эти два файла называют клиентской и серверной "заглушкой" (stub).
На данном рисунке отражен процесс создания интерфейса.

Вы также можете указать для компилятора MIDL "файл конфигурации приложения" (ACF).
Обычно в дополнение к компилятору MIDL вам необходимо использовать утилиту Uuidgen для создания UUID (Universal Unique Identifier = Универсальный Уникальный Идентификатор), что в принципе то же самое, что и GUID.
Генерация UUID-ов интерфейсов
Что такое UUID?
Интерфейсы должны быть определены в сети уникальным образом, чтобы клиенты могли их находить. В небольших сетях название интерфейса само по себе может быть достаточным для его идентификации. Однако для больших сетей это практически не осуществимо. Поэтому разработчики обычно назначают Универсальный Уникальный Идентификатор (UUID ) для каждого интерфейса. UUID – это строка, которая содержит набор шестнадцатеричных чисел. Каждый интерфейс имеет различный UUID.
В текстовом виде UUID представлен строкой, содержащей 8 шестнадцатеричных чисел, затем знак '–', затем три группы из 4 шестнадцатеричных чисел, разделенных знаком '-', затем '-', затем 12 шестнадцатеричных чисел. Вот пример правильной строки UUID:
bac6c-11d2-97cf-00c04f8eea45
Использование Uuidgen
Используйте утилиту Uuidgen для генерации ваших UUID-ов. Утилита Uuidgen создает UUID в формате IDL-файла или файла на языке C.
Вы можете использовать следующие ключи запуска утилиты Uuidgen:
Ключ | Описание |
/I | Создает UUID в виде шаблона IDL интерфейса |
/s | Создает UUID в виде шаблона C-интерфейса |
/o<имяфайла> | Имя результирующего файла |
/n<число> | Число генерируемых UUID-ов |
/v | Получить номер версии |
/h или /? | Получить справку по ключам запуска |
Обычно вы будете запускать утилиту Uuidgen следующим образом:
uuidgen –i –oMyApp. idl
Эта команда генерирует UUID и сохраняет его в виде IDL-файла, который мы можете использовать как шаблон. Если была использована указанная выше команда, то содержимое файла MyApp. idl будет выглядеть примерно следующим образом:
[
uuid(bac6c-11d2-97cf-00c04f8eea45),
version(1.0)
]
interface INTERFACENAME
{
}
Далее вам необходимо заменить название INTERFACENAME на название вашего интерфейса.
MIDL
Все интерфейсы для программ, использующих RPC, необходимо определять с помощью MIDL (Microsoft Interface Definition Language = Языка Определения Интерфейсов Microsoft).
Определение интерфейса с помощью MIDL
Файлы MIDL – это обычные текстовые файлы, которые вы можете создавать и редактировать в любом текстовом редакторе. Если вы генерируете UUID для вашего интерфейса, то обычно вы сохраняете результат в виде шаблона MIDL.
Все интерфейсы в MIDL начинаются с заголовка, который содержит список атрибутов интерфейса, и имя интерфейса. Атрибуты заключены в квадратные скобки. После заголовка интерфейса следует тело определение интерфейса, которое заключено в фигурные скобки. Вот пример простого интерфейса:
[
uuid(bac6c-11d2-97cf-00c04f8eea45),
version(1.0)
]
interface MyInterface
{
const unsigned short INT_ARRAY_LEN = 100;
void MyRemoteProc(
[in] int param1,
[out] int outArray[INT_ARRAY_LEN]
);
}
Типичными атрибутами интерфейса являются его UUID и номер версии интерфейса. Тело определения интерфейса должно содержать объявления всех процедур интерфейса. Оно также может содержать объявления типов данных и констант, требуемых интерфейсом.
Все параметры объявляемых процедур должны быть снабжены указателями направления передачи данных [in], [out] или [in, out] (данные передаются для удаленной процедуры, возвращаются удаленной процедурой, либо данный как передаются, так и возвращаются).
Компиляция MIDL-файла
Компилятор MIDL – это утилита командной строки, которая автоматически устанавливается вместе с Комплектом Разработчика для платформы Windows (Platform SDK) или поставляется вместе со средой разработки приложений Microsoft Visual Studio. Компилятор MIDL вызывается из командной строки набором команды midl и именем MIDL-файла. Убедитесь что каталог, содержащий компилятор MIDL, включен в пути поиска. Вот пример использования:
midl MyApp. idl
Вы также можете указывать ключи опций запуска. Так, например, чтобы явно указать расположение файла конфигурации приложения (ACF), можно использовать следующий вид команды:
midl /acf MyApp. acf MyApp. idl
Разрабатываем приложение-сервер
Чтобы создать сервер распределенного приложения, вам необходимо использовать файл заголовка и серверную заглушку, которые генерирует компилятор MIDL. Используйте файл заголовка в исходных кодах серверного приложения. Скомпилируйте серверную заглушку вместе с исходными файлами вашего приложения. Произведите компоновку результирующих объектных файлов с библиотеками импорта. Весь этот процесс отражен на следующей диаграмме:


Как можно видеть из приведенного выше рисунка, MIDL-файл MyApp. idl был использован для того, чтобы определить интерфейс. Компилятор MIDL, используя MyApp. idl, создал MyApp_s. c и MyApp. h. Он также создал файл клиентской заглушки, но в данный момент это пока не рассматривается. Исходный код серверной программы (в нашем случае – это MySrvr. c) должен подключать файл MyApp. h. Также нужно подключать файлы rpc. h и rpcndr. h.
Серверное приложение состоит их двух исходных файлов: MySrvr. c и RProcs. c. Файл MySrvr. c содержит функции, необходимые для запуска программы. А реализация удаленных процедур, предлагаемых сервером, содержится в файле RProcs. c.
Все три файла MySrvr. c, RProcs. c и MyApp. c были скомпилированы, в результате чело были получены объектные файлы. Эти объектные файлы были затем скомпонованы вместе с библиотекой времени выполнения (Run-time) RPC и другими необходимыми приложению библиотеками. В результате получили исполнимый файл сервера MySrvr. exe.
Если при компиляции вашего IDL файла не был указан совместимости с OSF (Open Software Foundation) /osf, то вам необходимо обеспечить функции для выделения и освобождения памяти.
Разрабатываем приложение-клиент
Разработка клиентского приложения RPC во многом похожа на разработку серверной программы. Точно также как и при разработке сервера, клиентское приложение должно использовать файл заголовка, который создает компилятор MIDL. Компилятор MIDL также создает файл клиентской заглушки на языке C. Вы должны скомпилировать этот файл и скомпоновать вместе с вашим клиентским приложением. Весь этот процесс отражен на следующей диаграмме:


Вначале необходимо определить интерфейс в файле MyApp. idl. Компилятор MIDL, используя MyApp. idl, создал MyApp_c. c – код клиентской заглушки и MyApp. h. Исходный код клиентской программы (в нашем случае – это MyClnt. c) должен подключать файл MyApp. h. Также нужно подключать файлы rpc. h и rpcndr. h.
Если при компиляции вашего IDL файла не был указан совместимости с OSF (Open Software Foundation) /osf, то вам необходимо обеспечить функции для выделения и освобождения памяти.
Обработка исключений
RPC использует тот же механизм обработки исключений, что и Windows API.
Макросы RpcTryFinally / RpcFinally / RpcEndFinally аналогичны конструкциям try-finally. А макросы RpcTryExcept / RpcExcept / RpcEndExcept аналогичны конструкциям try-except.
Коды ошибок исключений могут содержать коды ошибок с префиксами RPC_S_ и RPC_X, возвращаемые функциями RPC, и коды исключений самой Windows.
Исключений, которые возникают в серверном приложении, серверных заглушках и серверной run-time передаются клиенту. Исключения на транспортном уровне сервера клиенту не передаются. Для серверных процедур рекомендуется возвращать ошибки посредством исключений.
Подключение клиента к серверу
Чтобы клиент и сервер могли взаимодействовать, им необходимо установить сессию связи через сеть или сети, соединяющие их. После того, как они установят соединение, клиент может вызывать удаленные процедуры серверной программы, как если бы они были локальными относительно клиентского приложения.
Базовая терминология RPC
Чтобы лучше понять обсуждение процесса соединения клиента и сервера, необходимо знать следующие термины:
последовательность протокола (protocol sequence)
Для того, чтобы сетевые операционные системы могли взаимодействовать, необходимо, чтобы они "разговаривали" на одном языке. Эти языки называются последовательностями протокола. Клиентское и серверное приложение должны использовать последовательности протоколов, которые поддерживает сетевая среда, которая их соединяет. Microsoft RPC поддерживает разнообразие последовательностей протоколов.
хост-компьютер сервера или хост-система сервера
Серверная программа работает на хост-компьютере сервера. Однако, в большинстве литературных источников по клиент/серверным технологиям как серверную программу, так и хост-компьютер сервера называют просто "сервер". В результате не всегда понятно о чем именно идет речь.
конечная точка (endpoint)
Серверная программа ожидает клиентские запросы на порту или группе портов на хост-компьютере сервера. Хост-система сервера поддерживает базу данных таких портов, которые называются "конечными точками" в терминологии RPC. База данных конечных точек называется "таблица соответствия конечных точек".
[контекст] связывание[-я] (binding)
Клиентское приложение создает контекст связывания с сервером, чтобы сформировать сессию связи. Контекст связывания содержит всю необходимую информацию, необходимую клиентскому приложению для создания сессии.
Подготовка сервера к соединению
Серверное приложение, после начала выполнения, должно зарегистрировать интерфейс или интерфейсы, которые оно содержит, в run-time библиотеке RPC. После этого оно создает необходимую информацию для связывания. Серверная программа также должна зарегистрировать конечную или конечные точки, которые она будет прослушивать. После этого оно может начать ожидать клиентские запросы. Этот процесс проиллюстрирован на диаграмме.


В зависимости от выбранной стратегии разработки приложения, программа-сервер может выбрать другую последовательность шагов, предыдущий пример иллюстрирует оду из таких стратегий.
Регистрация интерфейса
Регистрации поддерживаемых интерфейсов обеспечивает диспетчеризацию запросов от клиентов к соответствующим процедурам сервера. Для регистрации интерфейсов можно использовать функцию RpcServerRegisterIf. Следующий фрагмент кода иллюстрирует ее использование:
RPC_STATUS status;
status = RpcServerRegisterIf(MyInterface_v1_0_s_ifspec, NULL, NULL);
Первый аргумент функции RpcServerRegisterIf – это структура, которую генерирует компилятор MIDL на основе IDL файла и которая определяет интерфейс к серверу. Второй и третий аргументы – это UUID и вектор точек входа, соответственно. В нашем примере – они установлены в значение NULL. В большинстве случаев ваша программа-сервер будет устанавливать эти значения в NULL. Второй и третий параметры используются при множественной реализации один и тех же процедур интерфейса.
Серверная программа может также использовать функцию RpcServerRegisterIfEx для регистрации интерфейса. Преимуществом этой функции является возможность указания функции обратного вызова (call-back) для обеспечения авторизации. Использование функций обратного вызова для реализации защищенного приложения – является рекомендуемой практикой.
Делаем доступ к серверу через сеть
Прежде чем серверное приложение сможет принимать запросы удаленных вызовов процедур, необходимо обеспечить доступ к нему через сеть. Чтобы это сделать, необходимо известить run-time RPC о своем желании принимать вызовы по одной или нескольким последовательностям протоколов. Выбор поддерживаемых серверным приложением последовательностей протоколов является важным выбором: различные последовательности протоколов обладают различными характеристиками. Сервера, которые ожидают локальные вызовы должны использовать ncalrpc. Сервера, который принимают вызовы по сети, должны использовать ncacn_ip_tcp.
В данном примере показано использование ncacn_ip_tcp:
RPC_STATUS status;
status = RpcServerUseProtseq(
L"ncacn_ip_tcp",
RPC_C_PROTSEQ_MAX_REQS_DEFAULT, // зависит от выбранной последовательности протокола
NULL); // всегда NULL
Первый аргумент функции RpcServerUseProtseq – это последовательность протокола. Второй аргумент зависит от выбранной последовательности протокола, он означает длину очереди ожидающих запросов и используется только для последовательности протокола ncacn_ip_tcp. В приведенном выше примере используется константа RPC_C_PROTSEQ_MAX_REQS_DEFAULT, что указывает на использование значения по умолчанию. Третий параметр – это дескриптор безопасности, который не используется.
Большинство серверных программ используют все возможные последовательности протоколов, поддерживаемые сетью. Для регистрации на всех последовательностях используйте функцию RpcServerUseAllProtseqs.
Вы также можете использовать следующие функции RpcServerUseProtseqEx, RpcServerUseProtseqEp или RpcServerUseProtseqEpEx.
После того как серверное приложение зарегистрирует как минимум одну последовательность протокола, сервера, которые используют динамические конечные точки, должны создать информацию для связывания для каждой последовательности протокола, которую они используют. Сервер хранит информацию о связывании в векторе связывания, который он затем может экспортировать службе сопоставления конечных точек.
Используйте функцию RpcServerInqBindings для получения вектора связывания для серверного приложения, как показано в следующем примере:
RPC_STATUS status;
RPC_BINDING_VECTOR *rpcBindingVector;
status = RpcServerInqBindings(&rpcBindingVector);
Единственный аргумент, передаваемый этой функции, – это указатель на указатель на структуру RPC_BINDING_VECTOR. RPC run-time динамически выделяет память для векторов связывания сохраняет адрес массива в переменной аргумента. Серверное приложение должно самостоятельно освободить память, после того как оно закончит работать с массивом, используя функцию RpcBindingVectorFree.
Регистрация конечных точек
Регистрация серверной программы в таблице соответствия конечных точек на хост-компьютере сервера позволяет клиентам определить, на какой конечной точке (обычно порт TCP/IP или именованный канал) "слушает" серверная программа. Для того, чтобы зарегистрировать себя в таблице соответствия конечных точек на хост-компьютере сервера, серверная программа вызывает функцию RpcEpRegister, как показано в следующем фрагменте кода:
RPC_STATUS status;
status = RpcEpRegister(
MyInterface_v1_0_s_ifspec,
rpcBindingVector,
NULL,
NULL);
Первый аргумент функции – это структура, которая представляет интерфейс, вы можете найти ее определение в заголовочном файле, который создает компилятор MIDL. Второй аргумент – это вектор связывания.
Помимо регистрации имен интерфейсов, серверное приложение может также зарегистрировать UUID-ы объектов в карте соответствия конечных точек. Информация о UUID-ах передается через третий аргумент. В нашем примере эта функциональность не используется, поэтому мы передаем NULL.
Последний аргумент – это строка комментария. Хотя rune-time RPC и не использует эту строку, все же рекомендуется указывать ее, так как это облегчает управление системой.
Ожидание запросов клиента
После того как серверное приложение зарегистрирует свои интерфейсы, создаст необходимую информацию связывания и зарегистрирует свои конечные точки, оно может начать ожидать запросы от клиентских приложений.
Для того, чтобы получать запросы к удаленным процедурам, серверная программа должна использовать функцию RpcServerListen, как показано в следующем примере:
RPC_STATUS status;
status = RpcServerListen(
1,
RPC_C_LISTEN_MAX_CALLS_DEFAULT,
0);
Сервер RPC использует один или несколько потоков, которые принимают клиентские запросы и доставляют их к процедурам, зарегистрированных интерфейсов. Первый аргумент – это минимально число потоков, которые необходимо создать. Этот параметр является всего лишь рекомендацией, run-time RPC может проигнорировать его.
Второй аргумент – это максимальное число одновременно обслуживаемых запросов. Если вы хотите использовать максимальное значение по умолчанию, укажите RPC_C_LISTEN_MAX_CALLS_DEFAULT.
Согласно спецификации DCE, функция RpcServerListen должна выолняться до тех пока она не получит сигнал остановки. Одним из расширений стандарта Microsoft является возможность разрешения обслуживания запросов и продолжение выполнение основного потока приложения. Если вы хотите использовать стандартное поведение согласно DCE, то укажите в третьем аргументе ноль.
Подготовка клиента к соединению
Чтобы установить сессию взаимодействия с сервером клиенту, который использует явные дескрипторы, необходимо создать дескриптор связывания. После того, как это будет сделано, run-time RPC найдет компьютер, на котором размещена серверная программа. После этого run-time находит конечные точки, на которых слушает серверная программа, и направляет вызовы к ним. Следующая диаграмма иллюстрирует этот процесс:


Создание дескриптора связывания
Клиентской программе необходимо создать дескриптор связывания, который указывает run-time RPC с каким сервером следует связываться и каким образом это следует делать.
Следующий фрагмент кода демонстрирует обычной подход к созданию десриптора связывания:
RPC_STATUS status;
unsigned short *StringBinding;
RPC_BINDING_HANDLE BindingHandle;
status = RpcStringBindingCompose(NULL, // UUID объекта
L"ncacn_ip_tcp", // Последовательность протокола
L"MyServer. ", // Имя DNS или NetBIOS для сервера
NULL,
NULL,
&StringBinding);
// Проверку на ошибки исключили, поэтому если ошибок нет то продолжаем
status = RpcBindingFromStringBinding(StringBinding, &BindingHandle);
//освободим память вне зависимости от успешности выполнения RpcBindingFromStringBinding
RpcStringFree(&StringBinding);
Вызов удаленной процедуры
После того как у клиентского приложения, использующего явные дискрипторы связывания, есть дескриптор связывания, можно вызывать удаленные процедуры.
Microsoft RPC также предлагает использование настраиваемых, неявных и автоматических дескрипторов связывания, что позволяет приложения получать различный контроль над процессом исполнения удаленных процедур.
Для того чтобы собственно вызвать удаленную процедуру, произведите обращение к функции клиентской заглушки.


