Windows RPC Demo实现
本文参考并整理以下相关文章
1. 《远程过程调用》 -百度百科
2. 《RPC 编程》 -http://www.ibm.com/developerworks/cn/aix/library/au-rpc_programming/
3. 《微软官方参考教程》 -http://blog.csdn.net/swanabin/article/details/18766209
1 概念
RPC:全称是“远程过程调用协议(Remote Procedure Call Protocol)”,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP、UDP或者命名管道,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
2 原理
RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息的到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行[1]。
不同的厂商实现了不同的RPC协议,显然我们此次用的是微软提供的。
3 实现
RPC的接口标准使用了IDL(Interface Description Language接口描述语言)语言标准描述,熟悉COM接口的用户应该一眼就能看出,因为它们的接口风格非常相似。相应微软的编译器是MIDL,通过IDL文件来定义RPC客户端与服务器之间的通信接口,只有通过这些接口客户端才能访问服务器。
下面我们就通过一个Demo来具体解释一下RCP编程的具体过程。
开发环境:Windows 7 SP1旗舰版
Visual Studio 2005
首先我们需要建立一个工程,我们选择Win32 Console程序RPCServer.exe,然后定义接口文件:IDL文件。
3.1 IDL文件
在工程中添加一个IDL文件,IDL文件由一个或多个接口定义组成,每一个接口定义都有一个接口头和一个接口体。接口头包含了使用此接口的信息,如UUID 。这此信息封装一对中括号之内。之后是interface关键字和接口名。接口体包含了C 风格的数据类型、函数原型和带属性的参数。这此参数的属性描述了数据如何在网络上传送。此例中,定义头中包含了uuid和版本号。版本号作兼容之用。客户端、服务端的版本只有兼容,才可以连接。
RPCServer.idl:
1 import "oaidl.idl"; 2 import "ocidl.idl"; 3 import "SelfDefine.h"; 4 5 [ 6 uuid( 551d88b0-b831-4283-a1cd-276559e49f28 ), 7 version( 1.0 ) 8 ] 9 10 interface RPCServer 11 { 12 error_status_t GetServerName( [out,size_is(len)]char *pszName, 13 [in]long len ); 14 error_status_t Add( [in]long num1, 15 [in]long num2, 16 [out]long* rtn ); 17 error_status_t ShutDown(); 18 }
定义了IDL文件之后,可以直接编译该IDL文件,但是编译该文件之后并没有自动生成相应的服务端和客户端的C文件。如果要达到自动生成这些C文件的目的,需要配置ACF文件。
3.2 ACF文件
ACF文件可以使用户定义自己的客户端或服务端的RPC接口。例如,如果你的客户端程序包含了一个复杂的数据结构,此数据结构只在本地机上有意义,那么你就可以在ACF文件中指定如何描述独立于机器的数据结构,使用数据结构用于远程过程调用。下面我们在ACF文件中定义一个handle类型,用来代表客户端与服务端的连接。[implicit_handle]属性允许客户端程序为它的远程过程调用选择一个服务端。ACF定义了此句柄为handle_t类型(MIDL基本数据类型)。MIDL编译器将绑定ACF文件指定的句柄名字RPCServer_IfHandle,放在生成的头文件RPCServer.h中。
RPCServer.acf:
1 [ 2 implicit_handle (handle_t RPCServer_IfHandle) 3 ] 4 5 interface RPCServer 6 { 7 }
当编译这些文件RPCServer.idl时,确保RPCServer.idl和RPCServer.acf在同一文件夹中。编译完成会生成三个文件:RPCServer.h、RPCServer_s.c、RPCServer_c.c,其中RPCServer.h、RPCServer_s.c供服务端使用,RPCServer_h.h、RPCServer_c.c供客户端使用。
3.3 SelfDefine.h文件
使用MIDL单独编译idl文件没有问题,但是在vs2005整个工程中编译时会提示如下所示错误:
fatal error C1189: #error : You need a Windows 2000 or later to run this stub because it uses these features:
该错误的官方原因现还不太清楚,解决该错误的就需要新建SelfDefine.h文件并定义如下宏:
#undef TARGET_IS_NT50_OR_LATER
#define TARGET_IS_NT50_OR_LATER 1
然后在idl文件中导入该头文件(import "SelfDefine.h";)。
SelfDefine.h:
1 #ifndef _SELF_DEFINE_H_ 2 #define _SELF_DEFINE_H_ 3 4 #undef TARGET_IS_NT50_OR_LATER 5 #define TARGET_IS_NT50_OR_LATER 1 6 7 typedef long MyHandle; 8 9 #endif
SelfDefine.h文件中还多了一句typedef long MyHandle;,如果不加入类似语句,MIDL编译器就会提示如下错误,真不知道这是什么原因。
error MIDL2183 : unexpected end of file found : SelfDefine.h
有了RPC接口文件,我们就可以编写服务程序,提供接口中定义的服务。
3.4 服务器端
根据用户的编码风格,可以在一个或多个独立的文件中实现这些远程过程。Demo中使用RPCServer.cpp源文件编写主要的服务端代码。
服务端调用RPC运行库函数RpcServerUseProtseqEp和RpcServerRegisterIf来建立有效的服务端。
服务端必须包含两个内存分配函数。这两个函数被服务端程序调用:midl_user_allocate和midl _user_free。这两个函数在服务端分配和释放内存,一般情况下midl _user_allocate和midl_user_free都会简单地封装C库函数malloc和free。
RPCServer.cpp:
1 #include "stdafx.h" 2 #include <Windows.h> 3 #include <stdlib.h> 4 #include <stdio.h> 5 #include "RPCServer_h.h" 6 7 #pragma comment(lib, "Rpcrt4.lib") 8 9 long StartService(); 10 11 int main( int argc, char* argv[] ) 12 { 13 StartService(); 14 return 0; 15 } 16 17 void __RPC_FAR * __RPC_USER midl_user_allocate( size_t len ) 18 { 19 return (malloc(len) ); 20 } 21 void __RPC_USER midl_user_free( void __RPC_FAR *ptr ) 22 { 23 free( ptr ); 24 } 25 26 long StartService() 27 { 28 unsigned char pszProtocolSequence[] = "ncacn_np"; 29 unsigned char *pszSecurity = NULL; 30 unsigned char pszEndPoint[] = "\pipe\RPCServer"; //命名管道 31 32 RPC_STATUS rpcStats = RpcServerUseProtseqEp( pszProtocolSequence, 33 RPC_C_LISTEN_MAX_CALLS_DEFAULT, 34 pszEndPoint, 35 pszSecurity ); 36 if ( rpcStats ) 37 exit( rpcStats ); 38 39 rpcStats = RpcServerRegisterIf( RPCServer_v1_0_s_ifspec, NULL, NULL ); 40 if ( rpcStats ) 41 exit( rpcStats ); 42 43 unsigned int fDontWait = false; 44 rpcStats = RpcServerListen( 1, RPC_C_LISTEN_MAX_CALLS_DEFAULT, fDontWait ); 45 if ( rpcStats ) 46 exit( rpcStats ); 47 48 return 0; 49 } 50 51 void StopService() 52 { 53 RPC_STATUS rpcStatus; 54 rpcStatus = RpcMgmtStopServerListening( NULL ); 55 rpcStatus = RpcServerUnregisterIf( NULL, NULL, FALSE ); 56 } 57 58 error_status_t GetServerName( 59 /* [size_is][out] */ unsigned char *pszName, 60 /* [in] */ long len) 61 { 62 strncpy( (char*)pszName, "RPCServer", len ); 63 pszName[len-1] = '