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

 import "oaidl.idl";
import "ocidl.idl";
import "SelfDefine.h"; [
uuid( 551d88b0-b831--a1cd-276559e49f28 ),
version( 1.0 )
] interface RPCServer
{
error_status_t GetServerName( [out,size_is(len)]char *pszName,
[in]long len );
error_status_t Add( [in]long num1,
[in]long num2,
[out]long* rtn );
error_status_t ShutDown();
}

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

 [
implicit_handle (handle_t RPCServer_IfHandle)
] interface RPCServer
{
}

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

 #ifndef _SELF_DEFINE_H_
#define _SELF_DEFINE_H_ #undef TARGET_IS_NT50_OR_LATER
#define TARGET_IS_NT50_OR_LATER 1 typedef long MyHandle; #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

 #include "stdafx.h"
#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>
#include "RPCServer_h.h" #pragma comment(lib, "Rpcrt4.lib") long StartService(); int main( int argc, char* argv[] )
{
StartService();
return ;
} void __RPC_FAR * __RPC_USER midl_user_allocate( size_t len )
{
return (malloc(len) );
}
void __RPC_USER midl_user_free( void __RPC_FAR *ptr )
{
free( ptr );
} long StartService()
{
unsigned char pszProtocolSequence[] = "ncacn_np";
unsigned char *pszSecurity = NULL;
unsigned char pszEndPoint[] = "\\pipe\\RPCServer"; //命名管道 RPC_STATUS rpcStats = RpcServerUseProtseqEp( pszProtocolSequence,
RPC_C_LISTEN_MAX_CALLS_DEFAULT,
pszEndPoint,
pszSecurity );
if ( rpcStats )
exit( rpcStats ); rpcStats = RpcServerRegisterIf( RPCServer_v1_0_s_ifspec, NULL, NULL );
if ( rpcStats )
exit( rpcStats ); unsigned int fDontWait = false;
rpcStats = RpcServerListen( , RPC_C_LISTEN_MAX_CALLS_DEFAULT, fDontWait );
if ( rpcStats )
exit( rpcStats ); return ;
} void StopService()
{
RPC_STATUS rpcStatus;
rpcStatus = RpcMgmtStopServerListening( NULL );
rpcStatus = RpcServerUnregisterIf( NULL, NULL, FALSE );
} error_status_t GetServerName(
/* [size_is][out] */ unsigned char *pszName,
/* [in] */ long len)
{
strncpy( (char*)pszName, "RPCServer", len );
pszName[len-] = '\0';
printf( "服务已经启动!\n" );
return ;
} error_status_t Add(
/* [in] */ long num1,
/* [in] */ long num2,
/* [out] */ long *rtn)
{
*rtn = num1 + num2;
return ;
} error_status_t ShutDown( void)
{
StopService();
printf( "服务已经关闭!\n" );
return ;
}

3.5 客户端

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

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

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

  RPCClient.cpp

 #include "stdafx.h"
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include "..\RPCServer\RPCServer_h.h"
#include "..\RPCServer\RPCServer_c.c" #pragma comment( lib, "Rpcrt4.lib" ) void CallServerFuntions(); int main(int argc, char* argv[])
{
unsigned char *pszUuid = NULL;
unsigned char pszProtocolSequence[] = "ncacn_np";
unsigned char *pszNetworkAddress = NULL;
unsigned char pszEndpoint[] = "\\pipe\\RPCServer";
unsigned char *pszOptions = NULL;
unsigned char *pszStringBinding = NULL; RPC_STATUS rpcStatus = RpcStringBindingCompose( pszUuid,
pszProtocolSequence,
pszNetworkAddress,
pszEndpoint,
pszOptions,
&pszStringBinding );
if ( rpcStatus )
exit( rpcStatus ); rpcStatus = RpcBindingFromStringBinding( pszStringBinding,
&RPCServer_IfHandle );
if ( rpcStatus )
exit( rpcStatus ); RpcTryExcept
{
CallServerFuntions();
}
RpcExcept( )
{
unsigned long ulCode = RpcExceptionCode();
printf( "抛出异常0x%lx = %ld。\n", ulCode, ulCode );
}
RpcEndExcept rpcStatus = RpcStringFree( &pszStringBinding );
if ( rpcStatus )
exit( rpcStatus ); rpcStatus = RpcBindingFree( &RPCServer_IfHandle );
if ( rpcStatus )
exit( rpcStatus );
return ; return ;
} void __RPC_FAR * __RPC_USER midl_user_allocate( size_t len )
{
return (malloc(len) );
}
void __RPC_USER midl_user_free( void __RPC_FAR *ptr )
{
free( ptr );
} void CallServerFuntions()
{
unsigned char pszName[] = {};
GetServerName( pszName, ); //获取服务名称
printf( "Server Name is: %s\n", pszName ); long num1 = ;
long num2 = ;
long nSum = ;
Add( num1, num2, &nSum ); //加法求值c
printf( "%d + %d = %d\n", num1, num2, nSum ); ShutDown(); //关闭服务
}

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

3.6 运行结果

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

Windows RPC Demo实现的更多相关文章

  1. Windows RPC

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

  2. golang rpc demo

    RPC工作流程图 1.调用客户端句柄:执行传送参数 2.调用本地系统内核发送网络消息 3.消息传送到远程主机 4.服务器句柄得到消息并取得参数 5.执行远程过程 6.执行的过程将结果返回服务器句柄 7 ...

  3. C# 创建Windows服务demo

    一.准备工作 1.操作系统:Windows 10 X64 2.开发环境:VS2017 3.编程语言:C# 4. .NET版本:.NET Framework 4.5 二.创建Windows Servic ...

  4. GStreamer Windows tutorial demo 开发环境配置

    GStreamer 示例程序在 Windows 环境配置时坑比较多,好不容易配置成功了,写篇文档分享一下安装的关键步骤 官方文档见:https://gstreamer.freedesktop.org/ ...

  5. C#Windows Forms (Demo.SYS)--xdd

    private void Show_background_picture()//随机更换背景 { ";//默认值 Random ran = new Random(); , );//返回一个1 ...

  6. WindowsService(Windows服务)开发步骤附Demo

    1.打开VS,新建项目,选择Windows服务,然后设置目录及项目名称后点击确定. 2.展开Service1服务文件,编写service1.cs类文件,不是Service1[设计].然后修改OnSta ...

  7. WindowsService(Windows服务)开发步骤附Demo 【转】

    转http://www.cnblogs.com/moretry/p/4149489.html 1.打开VS,新建项目,选择Windows服务,然后设置目录及项目名称后点击确定. 2.展开Service ...

  8. python通过protobuf实现rpc

    由于项目组现在用的rpc是基于google protobuf rpc协议实现的,所以花了点时间了解下protobuf rpc.rpc对于做分布式系统的人来说肯定不陌生,对于rpc不了解的童鞋可以自行g ...

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

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

随机推荐

  1. string字符串类型

    一次设置一个key-value 使用set命令可以一次设置一个key-value,使用get命令可以查询key所关联的字符串值.如下图所示. 一次设置多个key-value 使用mset命令可以设置多 ...

  2. Threading.Tasks.Task多线程 静态全局变量(字典) --只为了记录

    --------------------------------------------------------------后台代码---------------------------------- ...

  3. CentOS hadoop启动错误 JAVA_HOME is not set and could not be found

    ... Starting namenodes on [] localhost: Error: JAVA_HOME is not set and could not be found. localhos ...

  4. document.cookie的使用

    设置cookie每个cookie都是一个名/值对,可以把下面这样一个字符串赋值给document.cookie:document.cookie="userId=828";如果要一次 ...

  5. 十大谷歌Google搜索技巧分享

    前言:多数人在使用Google搜索的过程是非常低效和无谓的,如果你只是输入几个关键词,然后按搜索按钮,你将是那些无法得到Google全部信息的用户,在这篇文章中,Google搜索专家迈克尔.米勒将向您 ...

  6. tarjan求强联通分量 模板

    void tarjan(int u) { dfn[u]=low[u]=++dfs_clock; stack_push(u); for (int c=head[u];c;c=nxt[c]) { int ...

  7. php 判断是否 是手机访问

    //判断是否属手机 function is_mobile() { $user_agent = $_SERVER['HTTP_USER_AGENT']; $mobile_agents = Array(& ...

  8. MongoDB常用操作一查询find方法db.collection_name.find()

    来:http://blog.csdn.net/wangli61289/article/details/40623097 https://docs.mongodb.org/manual/referenc ...

  9. ios应用数据存储的常用方式 ios7.1应用沙盒

    归档:用某种格式保存某个对象,又称持久化. 1XML 属性列表plist归档(持久化) 2Preference(偏好设置) 3NSKeyedArchiver归档 4SQLite3 5Core Data ...

  10. High Performance Django

    构建高性能Django站点   性能 可用 伸缩 扩展 安全 build 1.审慎引入第三方库(是否活跃.是否带入query.是否容易缓存) 2.db:减少query次数 减少耗时query 减小返回 ...