当前位置:首页 > 操作系统 > Windows

Windows RPC Demo实现

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:

            <span> 1</span> import <span>"</span><span>oaidl.idl</span><span>"</span><span>;
</span><span> 2</span> import <span>"</span><span>ocidl.idl</span><span>"</span><span>;
</span><span> 3</span> import <span>"</span><span>SelfDefine.h</span><span>"</span><span>;
</span><span> 4</span><span> 5</span><span>[
</span><span> 6</span>     uuid( 551d88b0-b831-<span>4283</span>-a1cd-<span>276559e49f28 ),
</span><span> 7</span>     version( <span>1.0</span><span> )
</span><span> 8</span><span>]
</span><span> 9</span><span>10</span><span>interface</span><span> RPCServer
</span><span>11</span><span>{
</span><span>12</span>     error_status_t GetServerName( [<span>out</span>,size_is(len)]<span>char</span> *<span>pszName,
</span><span>13</span>                                      [<span>in</span>]<span>long</span><span> len );
</span><span>14</span>     error_status_t Add( [<span>in</span>]<span>long</span><span> num1,
</span><span>15</span>                           [<span>in</span>]<span>long</span><span> num2,
</span><span>16</span>                           [<span>out</span>]<span>long</span>*<span> rtn );
</span><span>17</span><span>    error_status_t ShutDown();
</span><span>18</span> }

  定义了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:

            <span>1</span>
            <span>[
</span>
            <span>2</span>
            <span>    implicit_handle (handle_t RPCServer_IfHandle)
</span>
            <span>3</span>
            <span>]
</span>
            <span>4</span>
            <span>5</span>
            <span>interface</span>
            <span> RPCServer
</span>
            <span>6</span>
            <span>{
</span>
            <span>7</span> }

  当编译这些文件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:

            <span>1</span>
            <span>#ifndef _SELF_DEFINE_H_
</span>
            <span>2</span>
            <span>#define</span> _SELF_DEFINE_H_
<span>3</span><span>4</span><span>#undef</span> TARGET_IS_NT50_OR_LATER
<span>5</span><span>#define</span> TARGET_IS_NT50_OR_LATER 1
<span>6</span><span>7</span> typedef <span>long</span><span> MyHandle;
</span><span>8</span><span>9</span><span>#endif</span>

  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:

            <span> 1</span> #include <span>"</span><span>stdafx.h</span><span>"</span><span> 2</span> #include <Windows.h>
<span> 3</span> #include <stdlib.h>
<span> 4</span> #include <stdio.h>
<span> 5</span> #include <span>"</span><span>RPCServer_h.h</span><span>"</span><span> 6</span><span> 7</span><span>#pragma</span> comment(lib, "Rpcrt4.lib")
<span> 8</span><span> 9</span><span>long</span><span> StartService();
</span><span>10</span><span>11</span><span>int</span> main( <span>int</span> argc, <span>char</span>*<span> argv[] )
</span><span>12</span><span>{
</span><span>13</span><span>    StartService();
</span><span>14</span><span>return</span><span>0</span><span>;
</span><span>15</span><span>}
</span><span>16</span><span>17</span><span>void</span> __RPC_FAR *<span> __RPC_USER midl_user_allocate( size_t len )
</span><span>18</span><span>{
</span><span>19</span><span>return</span> (<span>malloc</span><span>(len) );
</span><span>20</span><span>}
</span><span>21</span><span>void</span> __RPC_USER midl_user_free( <span>void</span> __RPC_FAR *<span>ptr )
</span><span>22</span><span>{
</span><span>23</span><span>free</span><span>( ptr );
</span><span>24</span><span>}
</span><span>25</span><span>26</span><span>long</span><span> StartService()
</span><span>27</span><span>{
</span><span>28</span>     unsigned <span>char</span> pszProtocolSequence[] = <span>"</span><span>ncacn_np</span><span>"</span><span>;
</span><span>29</span>     unsigned <span>char</span> *pszSecurity =<span> NULL;
</span><span>30</span>     unsigned <span>char</span> pszEndPoint[] = <span>"</span><span>pipeRPCServer</span><span>"</span>; <span>//</span><span>命名管道</span><span>31</span><span>32</span>     RPC_STATUS rpcStats =<span> RpcServerUseProtseqEp( pszProtocolSequence,
</span><span>33</span><span>        RPC_C_LISTEN_MAX_CALLS_DEFAULT,
</span><span>34</span><span>        pszEndPoint,
</span><span>35</span><span>        pszSecurity );
</span><span>36</span><span>if</span><span> ( rpcStats )
</span><span>37</span><span>        exit( rpcStats );
</span><span>38</span><span>39</span>     rpcStats =<span> RpcServerRegisterIf( RPCServer_v1_0_s_ifspec, NULL, NULL ); 
</span><span>40</span><span>if</span><span> ( rpcStats ) 
</span><span>41</span><span>        exit( rpcStats );
</span><span>42</span><span>43</span>     unsigned <span>int</span> fDontWait = <span>false</span><span>;
</span><span>44</span>     rpcStats = RpcServerListen( <span>1</span><span>, RPC_C_LISTEN_MAX_CALLS_DEFAULT, fDontWait );
</span><span>45</span><span>if</span><span> ( rpcStats )
</span><span>46</span><span>        exit( rpcStats );
</span><span>47</span><span>48</span><span>return</span><span>0</span><span>;
</span><span>49</span><span>}
</span><span>50</span><span>51</span><span>void</span><span> StopService()
</span><span>52</span><span>{
</span><span>53</span><span>    RPC_STATUS rpcStatus;
</span><span>54</span>     rpcStatus =<span> RpcMgmtStopServerListening( NULL );
</span><span>55</span>     rpcStatus =<span> RpcServerUnregisterIf( NULL, NULL, FALSE );
</span><span>56</span><span>}
</span><span>57</span><span>58</span><span>error_status_t GetServerName( 
</span><span>59</span><span>/*</span><span> [size_is][out] </span><span>*/</span> unsigned <span>char</span> *<span>pszName,
</span><span>60</span><span>/*</span><span> [in] </span><span>*/</span><span>long</span><span> len)
</span><span>61</span><span>{
</span><span>62</span>     strncpy( (<span>char</span>*)pszName, <span>"</span><span>RPCServer</span><span>"</span><span>, len );
</span><span>63</span>     pszName[len-<span>1</span>] = <span>‘</span><span></span><span>‘</span><span>;
</span><span>64</span>     printf( <span>"</span><span>服务已经启动!
</span><span>"</span><span> );
</span><span>65</span><span>return</span><span>0</span><span>;
</span><span>66</span><span>}
</span><span>67</span><span>68</span><span>error_status_t Add( 
</span><span>69</span><span>/*</span><span> [in] </span><span>*/</span><span>long</span><span> num1,
</span><span>70</span><span>/*</span><span> [in] </span><span>*/</span><span>long</span><span> num2,
</span><span>71</span><span>/*</span><span> [out] </span><span>*/</span><span>long</span> *<span>rtn)
</span><span>72</span><span>{
</span><span>73</span>     *rtn = num1 +<span> num2;
</span><span>74</span><span>return</span><span>0</span><span>;
</span><span>75</span><span>}
</span><span>76</span><span>77</span> error_status_t ShutDown( <span>void</span><span>)
</span><span>78</span><span>{
</span><span>79</span><span>    StopService();
</span><span>80</span>     printf( <span>"</span><span>服务已经关闭!
</span><span>"</span><span> );
</span><span>81</span><span>return</span><span>0</span><span>;
</span><span>82</span> }

3.5 客户端

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

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

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

  RPCClient.cpp:

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

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

3.6 运行结果

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

 

原文:http://www.cnblogs.com/wanghaiyang1930/p/4469222.html


【说明】本文章由站长整理发布,文章内容不代表本站观点,如文中有侵权行为,请与本站客服联系(QQ:254677821)!

相关教程推荐

其他课程推荐