转载

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] = '\0';
64 printf( "服务已经启动!\n" );
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( "服务已经关闭!\n" );
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 "..\RPCServer\RPCServer_h.h"
6 #include "..\RPCServer\RPCServer_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。\n", 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\n", 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\n", num1, num2, nSum );
78
79 ShutDown(); //关闭服务
80 }

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

3.6 运行结果

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

 
标签: RPC编程实现

Windows RPC的更多相关文章

  1. Windows RPC Demo实现

    Windows RPC Demo实现 本文参考并整理以下相关文章 1. <远程过程调用> -百度百科 2. <RPC 编程> -http://www.ibm.com/devel ...

  2. Windows Error Code(windows错误代码详解)

    0 操作成功完成. 1 功能错误. 2 系统找不到指定的文件. 3 系统找不到指定的路径. 4 系统无法打开文件. 5 拒绝访问. 6 句柄无效. 7 存储控制块被损坏. 8 存储空间不足,无法处理此 ...

  3. 如何设计一个RPC系统

    版权声明:本文由韩伟原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/162 来源:腾云阁 https://www.qclou ...

  4. Windows系统错误代码大全

    1 Microsoft Windows 系统错误代码简单分析: 0000 操作已成功完成.0001 错误的函数. 0002 系统找不到指定的文件. 0003 系统找不到指定的路径. 0004 系统无法 ...

  5. Windows 错误代码

    Error Messages for Windows http://www.gregorybraun.com/MSWINERR.ZIP Server 4.0 Error Messages   Code ...

  6. 从Windows角度看Mac OS X上的软件开发

    如果原来从事Windows软件开发,想跨足或转换至Mac OS X环境,需要知道那些东西?有什么知识技能可以快速运用在Mac OS X环境上的?这两个问题应该是Windows开发者进入Mac OS X ...

  7. 如何设计一个 RPC 系统

    本文由云+社区发表 RPC是一种方便的网络通信编程模型,由于和编程语言的高度结合,大大减少了处理网络数据的复杂度,让代码可读性也有可观的提高.但是RPC本身的构成却比较复杂,由于受到编程语言.网络模型 ...

  8. windows 系统错误码总结

    windows 错误码大全: 操作成功完成. 功能错误. 系统找不到指定的文件. 系统找不到指定的路径. 系统无法打开文件. 拒绝访问. 句柄无效. 存储控制块被损坏. 存储空间不足,无法处理此命令. ...

  9. Windows错误码大全

    0000 操作已成功完成.0001 错误的函数.0002 系统找不到指定的文件.0003 系统找不到指定的路径.0004 系统无法打开文件.0005 拒绝访问.0006 句柄无效.0007 存储区控制 ...

随机推荐

  1. Eclipse在Jar形成和应用程序包

    最近的熟悉Java语言.在学习过程中Eclipse经常使用再熟悉它.本文简单说下Jar形成和应用程序包. Java在Jar相当于包C/C++该lib库,它是.class文件打包:经常使用Jar包有AP ...

  2. Java读取图像和网络存储

    该公司最近在搞一个Web工程,需要下载网络图片,那么既然恢复了一些最基本的东西.数据传输不同的流,简单,很容易下载网络打破了样品的图片,代码非常easy.贡献给大家! 结论,图片主要就四步: 1:拿到 ...

  3. 白学jquery Mobile《构建跨平台APP:jQuery Mobile移动应用实战》连续7-电话问卷调查

    [例7-3  文本编辑框创建一个简单的调查问卷] 01     <!DOCTYPEhtml> 02     <html> 03     <head> 04     ...

  4. 【干货】免费获得WebStorm软件

    内容提要: 1.WebStorm简介 2.如何免费获得WebStorm 3.利用学生身份免费获得正式版WebStorm WebStorm简介 WebStorm 是一款前端开发 IDE(集成开发环境), ...

  5. vs2012 网站无法使用自定义服务器的解决方法

    我已经习惯新建一个Asp.net网站时把它挂载在IIS下调试运行,在使用Visual Studio 2012后,新建网站配置启动选项时,自定义服务器居然不可用 原来是Visual Studio 201 ...

  6. 【Android进阶】Android程序与JavaScript之间的简单调用

    本篇将讲解一个简单的Android与JavaScript之间的简单调用的小程序 效果图 工程结构 HTMLActivity.java代码 package com.example.javatojs; i ...

  7. 证明 poj 1014 模优化修剪,部分递归 有错误

    这个问题是存在做.我发现即使是可行的一个问题,但不一定正确. 大部分数据疲软,因为主题. id=1014">poj 1014 Dividing 题目大意:有6堆石头,权重分别为1 2 ...

  8. 【Android进阶】ZXing android 错误(Could not find class 'com.google.zxing.ResultPoint)

    解决方法: 1.右键工程Build path, java build path,选择libraries 在右边的按钮中点击"Add Library" 选择"User li ...

  9. Oracle之Check约束实例具体解释

    Oracle | PL/SQL Check约束使用方法具体解释 1. 目标 实例解说在Oracle中怎样使用CHECK约束(创建.启用.禁用和删除) 2. 什么是Check约束? CHECK约束指在表 ...

  10. JVM的参数详解(转)

    12年毕业到先在处理第一年外这几年纯属于打酱油,当初自学Java然后就出来找工作了,还有第一家面试就通过了挺幸运的 但之后的这段时间一直是处于吃老本的状态.最近心情真的很不好,各种黄老邪!一直处于堕落 ...