RPC远程过程调用实例剖析之二
paddy102
Para4. 一个计算阶层的RPC实例开发过程
Microsoft提供了分布式计算的模型支持. RPC标准最初是作为OSF(开放软件基础)﹑DCE(分布式计算环境)规范的一部分. RPC的Microsoft实现与其它的RPC的DCE实现兼容, 例如UNIX服务器. 本节将介绍RPC的Microsoft实现. 文中涉及的一些概念如IDL(interface definition language)等请朋友们自己查找资料理解.
本程序由以下几个文件组成:
RpcFact.idl
RpcFact.acf
RpcFact.h
RpcFact_c.c
RpcFact_s.c
RpcFact.c
memstub.h
下面结合实现原理一一道来:
1. 定义接口 (得到RpcFact.idl文件)
创建一个.idl文件, 为应用程序的远地函数定义一个接口.
// File: RpcFact.idl
// interface header
[
uuid (C16F6562-520D-11D0-B338-444553540000), // universally unique identifier,唯一识别这个接// 口, 它可以被服务器用来注册接口, 以使客户机可以定位这个特殊接口. uuid由5位数字组
// 成,你可以用Microsoft的uuidgen.exe来得到它.
version (1.0), // 指定版本号, 那么在同一个网络上就可以同时拥有一个RPC接口的不同版本
endpoint(“ncalrpc: [myFactorial]”) // 接口的端点, 它给出了将会使用的网络协议的类型和用来接
// 收接口的请求的地址和端口, ncalrpc 表示Local procedure call
]
// interface text, 指定了组成这个接口的函数, 函数类型不支持int 和void*
interface rpcfactorial
{
long RpcFactorial([in] long nVal); // 出现[ ]的部分表示属性
}
// end File RpcFact.idl
2. 应用程序属性配置文件 (得到RpcFact.acf文件)
.acf文件用来指定应用程序的选项, 这些属性告诉client stub如何对接口请求的捆绑进行处理, 这个问题稍后介绍.
// RpcFact.acf
[
auto_handle // 相关属性
]
interface rpcfactorial
{
}
// end File RpcFact.acf
3. 用MIDL编译器编译RpcFact.idl文件
第一种方式, midl.exe /c_text /ms_text /app_config RpcFact.idl (详细了解请在dos下运行midl.exe /?获取).
第二种方式, 先将RpcFact.idl插入你的Project中, 在Projects—Settings中选取左边框内的RpcFact.idl文件, 选择右边的Custom Build页面, 位IDL文件指定一个用户定制的编译器. 可以键入如下命令:
midl.exe /c_text /ms_text /app_config $ (InputPath)
经过编译之后, 会生成这几个文件: RpcFact.h、RpcFact_s.c 和RpcFact_c.c
4. 开发RPC服务器
定义好了一个接口之后, 就需要创建一个响应接口请求的进程, 执行请求的操作并返回所有结果给client. 这些任务就由RPC服务器来完成.
(1) 执行计算的过程
本例中就是阶层函数的实现, 我把它单独放在一个RpcFact.c文件中.
// File: RpcFact.c
#include <windows.h>
#include “RpcFact.h” // 用MIDL编译器生成
long Factorial (long nVal)
{
long nResult = 1;
for (; nVal>0; nVal--)
nResult *= nVal;
return nResult;
}
// end File RpcFact.c
(2) RPC服务器实体
使RpcFact.c真正对RPC有用的工作是在服务器实体(RpcFact_s.c)中实现的. 实体内的代码负责建立RPC接口的捆绑, 并用名称服务程序来注册它们及侦听RPC请求.
// File: RpcFact_s.c
#include <windows.h>
#include <iostream.h>
#include <rpc.h>
#include “RpcFact.h”
#include “memstub.h” // 实现RPC函数的存储分配, 稍后介绍
main()
{
RPC_BINDING_VECTOR* bindVector;
RPC_STATUS lRetVal;
// 注册所有可行的协议序列, 所支持的协议由在RpcFact.idl的端点定义中的字符串指定
// 参数rpcfactorial_v1_0_s_ifspec定义在RpcFact.h中, 由MIDL编译生成
if (lRetVal = RpcServerUseAllProtseqsIf(1, rpcfactorial_v1_0_s_ifspec, NULL))
{
cout<<”Error in RpcServerUseAllProtseqsIf”<<lRetVal<<endl;
return 1;
}
// 注册所支持的接口
if (RpcServerRegisterIf(rpcfactorial_v1_0_s_ifspec, NULL, NULL))
{
cout<<”Error in RpcServerRegister”<<endl;
return 1;
}
// 将接口的捆绑输出到名称服务程序
if (RpcServerInqBindings(&bindVector))
{
cout<<”Error in RpcServerInqBindings”<<endl;
return 1;
}
if (RpcServerBindingExport(RPC_C_NS_SYNTAX_DEFAULT, (UCHAR*)”/.: /autorpc”, rpcfactorial_v1_0_s_ifspec, bindVector, NULL))
{
cout<<”Error in RpcServerBidingExport”<<endl;
return 1;
}
// 侦听RPC请求
cout<<”Calling RpcServerListen”<<endl;
if (RpcServerListen(1, 5, FALSE))
{
cout<<”Error in RpcServerListen”<<endl;
return 1;
}
return 0;
}// end main
// end File RpcFact_s.c
// File: memstub.h
// 以下两个函数在RpcFact.h中已经声明
void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t len)
{
return (new (unsigned char [len]));
}
void __RPC_USER MIDL_user_free( void __RPC_FAR* ptr)
{
delete ptr;
}
// end File memstub.h
(3) 建立服务器
建立服务器时, 你需要确保连接server stub函数(即RpcFact_s.c)编译后的.obj文件. 此外还得添加两个RPC运行库(rpcns4.lib和rpcrt4.lib).
5. 开发RPC客户机
// File: RpcFact_c.c
#include <windows.h>
#include <iostream.h>
#include <rpc.h>
#include <stdlib.h>
#include “RpcFact.h”
#include “memstub.h”
main()
{
long nVal;
cout<<”Calling RPC Factorial”<<endl;
nVal = RpcFactorial(5);
cout<<”RpcFact returns: “<<nVal<<endl;
return 0;
}
// end File RpcFact_c.c
至此, 一个简单的RPC调用示例已经完成, 文中没讲清楚的地方请参看[1][2][3]列出的书籍, 或者你也可以与我联系.
本文的后续篇将继续介绍RPC中的参数传递﹑捆绑到RPC服务器的几种方式以及RPC的异常处理.
参考书籍:
[1] David Bennett等著. <<Visual C++ 5开发人员指南>>. 机械工业出版社
[2] <<分布式操作系统>>
[3] MSDN 6.0