• Windows RPC


    转载

    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] = '';
    64     printf( "服务已经启动!
    " );
    65     return 0;
    66 }
    67 
    68 error_status_t Add( 
    69                    /* [in] */ long num1,
    70                    /* [in] */ long num2,
    71                    /* [out] */ long *rtn)
    72 {
    73     *rtn = num1 + num2;
    74     return 0;
    75 }
    76 
    77 error_status_t ShutDown( void)
    78 {
    79     StopService();
    80     printf( "服务已经关闭!
    " );
    81     return 0;
    82 }
    复制代码

    3.5 客户端

      类似服务器,我们也需要新建一个Win32 Console程序-RPCClient.exe,作为客户端。

      客户端程序调用运行时函数来建立一个指向服务端的句柄,在远程过程调用完成后,释放这个句柄。函数RpcStringBindingCompose()生成一个以字符串表示的绑定句柄(bindinghandle)并为此字符串分配内存。因为服务指定了使用本地命名管道作为网络通信介质,所以客户端在建立于服务器的连接时也必须指定相同的管道参数。

      函数RpcBindingFromStringBinding()从字符串表示的绑定句柄,创建了一个服务绑定句柄RPCServer_IfHandle。绑定之后客户端便可以请求服务器的服务(调用服务器的函数)。

      RPCClient.cpp

    复制代码
     1 #include "stdafx.h"
     2 #include <windows.h>
     3 #include <stdlib.h>
     4 #include <stdio.h>
     5 #include "..RPCServerRPCServer_h.h"
     6 #include "..RPCServerRPCServer_c.c"
     7 
     8 #pragma comment( lib, "Rpcrt4.lib" )
     9 
    10 void CallServerFuntions();
    11 
    12 int main(int argc, char* argv[])
    13 {
    14     unsigned char *pszUuid = NULL;
    15     unsigned char pszProtocolSequence[] = "ncacn_np";
    16     unsigned char *pszNetworkAddress = NULL;
    17     unsigned char pszEndpoint[] = "\pipe\RPCServer";
    18     unsigned char *pszOptions = NULL;
    19     unsigned char *pszStringBinding = NULL;
    20 
    21     RPC_STATUS rpcStatus = RpcStringBindingCompose( pszUuid,
    22                                                     pszProtocolSequence,
    23                                                     pszNetworkAddress,
    24                                                     pszEndpoint,
    25                                                     pszOptions,
    26                                                     &pszStringBinding );
    27     if ( rpcStatus )
    28         exit( rpcStatus );
    29 
    30     rpcStatus = RpcBindingFromStringBinding( pszStringBinding,
    31                                              &RPCServer_IfHandle );
    32     if ( rpcStatus )
    33         exit( rpcStatus );
    34 
    35     RpcTryExcept
    36     {
    37         CallServerFuntions();
    38     }
    39     RpcExcept( 1 )
    40     {
    41         unsigned long ulCode = RpcExceptionCode();
    42         printf( "抛出异常0x%lx = %ld。
    ", ulCode, ulCode );
    43     }
    44     RpcEndExcept
    45 
    46     rpcStatus = RpcStringFree( &pszStringBinding );
    47     if ( rpcStatus )
    48         exit( rpcStatus );
    49 
    50     rpcStatus = RpcBindingFree( &RPCServer_IfHandle );
    51     if ( rpcStatus )
    52         exit( rpcStatus );
    53     return 0;
    54 
    55     return 0;
    56 }
    57 
    58 void __RPC_FAR * __RPC_USER midl_user_allocate( size_t len )
    59 {
    60     return (malloc(len) );
    61 }
    62 void __RPC_USER midl_user_free( void __RPC_FAR *ptr )
    63 {
    64     free( ptr );
    65 }
    66 
    67 void CallServerFuntions()
    68 {
    69     unsigned char pszName[64] = {0};
    70     GetServerName( pszName, 64 ); //获取服务名称
    71     printf( "Server Name is: %s
    ", pszName );
    72     
    73     long num1 = 2;
    74     long num2 = 2;
    75     long nSum = 0;
    76     Add( num1, num2, &nSum ); //加法求值c
    77     printf( "%d + %d = %d
    ", num1, num2, nSum );
    78     
    79     ShutDown(); //关闭服务
    80 }
    复制代码

      具体函数的详细解释还需要参考MSDN,参政正确掌握RPC变成技巧。

    3.6 运行结果

      客户端分别调用了服务器的GetServerName()、Add()和Shutdown()函数,获取了服务器的名称、计算了两个数据的和,最后关闭了服务器。小小的Demo完成了RPC通信的整个过程,下图是Demo运行的效果图。

     

     

     

     

    标签: RPC编程实现

  • 相关阅读:
    编写高质量代码改善C#程序的157个建议——建议7: 将0值作为枚举的默认值
    编写高质量代码改善C#程序的157个建议——建议6: 区别readonly和const的使用方法
    编写高质量代码改善C#程序的157个建议——建议5: 使用int?来确保值类型也可以为null
    编写高质量代码改善C#程序的157个建议——建议4: TryParse比Parse好
    基于cookie实现用户验证
    页面分页自定义插件
    DOM详习讲解
    HTML标签详细讲解
    Tornado模板配置
    biginteger转Long
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/4471026.html
Copyright © 2020-2023  润新知