RPC overviews-1

文章目录

  • 1. RPC模型
    • 1.1 编程模型
    • 1.2 分布式模型
    • 1.3 RPC如何运作
    • 1.4 RPC组件
  • 2. RPC环境
  • 3. 构建RPC应用程序
    • 3.1 开发接口
      • acf
    • 3.2 开发服务端
    • 3.3 开发客户端
    • 3.4 异常处理
  • 4. 连接客户端和服务端
    • 4.1 基本RPC绑定术语
    • 4.2 服务器如何准备连接
      • 4.2.1 注册接口(有多余)
      • 4.2.2 使服务器在网络上可用
      • 4.2.3 注册端点(实际无)
      • 4.2.4 监听
      • 4.2.5 代码
    • 4.3 客户端如何建立连接
      • 4.3.1 创建绑定句柄
      • 4.3.2 进行RPC
      • 4.3.3 查找服务器程序
      • 4.3.4 将呼叫发送到服务器程序
      • 4.3.5 代码

1. RPC模型

RPC属于本机或网络IPC机制。

3种进程:32,64,wow64

1.1 编程模型

RPC中,函数调用用多了2种元素:

  • idl/acf文件,描述调用双方数据交换和传参机制;
  • 运行时API

1.2 分布式模型

1.3 RPC如何运作

RPC overviews-1_第1张图片

如图所示,客户端应用程序调用本地stub/存根过程,stub代码不是包含实现远程过程的实际代码,而是:

  • 从客户端地址空间检索所需的参数。
  • 根据需要将参数转换为标准NDR格式,以通过网络进行传输。
  • 调用RPC客户端运行时库中的函数,以将请求及其参数发送到服务器。

服务器执行以下步骤来调用远程过程。

  1. 服务器RPC运行时库函数接受请求并调用服务器stub过程。
  2. 服务器stub从网络缓冲区中检索参数,并将其从网络传输格式转换为服务器所需的格式。
  3. 服务器stub在服务器上调用实际过程。

远程过程,可能会生成输出参数和返回值,类似的步骤发回给客户端。

  1. 远程过程将其数据返回到服务器存根。
  2. 服务器stub将输出参数转换为通过网络传输所需的格式,然后将其返回给RPC运行时库函数。
  3. 服务器RPC运行时库功能将网络上的数据传输到客户端计算机。

客户端通过接受网络上的数据并将其返回给调用函数来完成该过程。

  1. 客户端RPC运行时库接收远程过程返回值,并将其返回给客户端stub。
  2. 客户端stub将数据从其NDR转换为客户端计算机使用的格式。存根将数据写入客户端内存,并将结果返回到客户端上的调用程序。
  3. 调用过程继续进行,就好像该过程已在同一台计算机上被调用一样。

运行时库分为两个部分:

  • 与应用程序链接的导入库
  • 作为动态链接库(DLL)实现的RPC运行时库。

1.4 RPC组件

PC包含以下主要组件:

  • MIDL编译器
  • 运行时库和头文件
  • 名称服务提供商(有时称为定位器)
  • 端点映射器(有时称为端口映射器)

MIDL编译器生成将本地过程调用转换为远程过程调用的存根。存根是占位符函数,用于调用运行时库函数,该库函数管理远程过程调用。

2. RPC环境

winsdk中包含:

  • c/c++头文件
  • RPC库
  • 样例程序
  • RPC参考帮助文件
  • uuidgen工具

系统则包含:

  • RPC运行时DLL
  • microsoft locator(vista及更高版本不支持)
  • RPC端点映射服务

导入库:rpcns4.lib, rpcrt4.lib

3. 构建RPC应用程序

根据平台、编译器和api库可能有所不同。

RPC overviews-1_第2张图片

  1. 开发接口。
  2. 开发实现该接口的服务器。
  3. 开发使用该接口的客户端。

3.1 开发接口

接口由接口名称,一些属性,可选类型或常量定义以及一组过程声明组成。每个过程声明必须包含一个过程名称,返回类型和参数列表。

接口定义在idl文件中,MIDL编译器生成1个头文件,和两个C源文件作为客户端和服务器stub。

默认情况下,客户端和服务器存根具有相同的名称,如果客户端与服务器存根链接,则可能引起问题,反之亦然。使用MIDL / prefix选项可防止发生此常见错误。

RPC overviews-1_第3张图片

还需要一个应用程序配置文件(ACF),用来输入到MIDL编译器。


Uuidgen生成 通用唯一标识符(UUID,可与术语GUID互换)

网络上的接口必须有唯一标识才能被客户端找到。UUID是十六进制数字字符串,以下示例是有效的UUID字符串:

ba209999-0c6c-11d2-97cf-00c04f8eea45

各成员长度比8,4,4,4,12,即一共32个十六进制数。

空的UUID被称为nil UUID,而不是NULL UUID。

uuidgen -i -oMyApp.idl

注意不要加空格

生成内容:

[
uuid(9315f815-63b0-4852-955d-7fcf0d786f25),
version(1.0)
]
interface INTERFACENAME
{

}

也可以用vs工具里的Guid生成工具。

然后替换INTERFACENAME,完成主体即可.

[
	implicit_handle(handle_t bindingHandle)
]
interface MyInterface
{
	MyRemoteProc();
}

可以用midl命令行编译,或者vs直接编译

midl MyApp.idl

然后目录下就会生成MyApp_c.c, MyApp_s.c, MyApp_h.h

acf

acf这里msdn上没有,因为后来客户端报错,1722, The RPC server is unavailable.

在ACF文件中定义一个handle类型,用来代表客户端与服务端的连接。

[
	implicit_handle(handle_t bindingHandle)
]
interface MyInterface
{
}

3.2 开发服务端

RPC overviews-1_第4张图片

midl生成的头文件中,实际上还包含了两个rpc头文件:

#include "rpc.h"
#include "rpcndr.h"

3.3 开发客户端

RPC overviews-1_第5张图片

如果不以OSF兼容模式/ osf编译IDL文件,则服务端和客户端程序必须提供分配内存的功能和取消分配内存的功能。

3.4 异常处理

以下等同try-finally

RpcTryFinally / RpcFinally / RpcEndFinally

以下等同try-except

 RpcTryExcept / RpcExcept / RpcEndExcept

vista及更高版本中,建议使用RpcExceptionFilter处理异常。

下图展示了如何将异常从服务器返回到客户端:

RPC overviews-1_第6张图片

4. 连接客户端和服务端

4.1 基本RPC绑定术语

协议顺序,Protocol Sequence

终点,endpoint,服务器监听的端口。 服务机上的数据库称为端点映射。

4.2 服务器如何准备连接

RPC overviews-1_第7张图片

4.2.1 注册接口(有多余)

RPC_STATUS status;
status = RpcServerRegisterIf(MyInterface_v1_0_s_ifspec, NULL, NULL);

第一个参数是由midl生成的。

//MyApp_h.h
extern RPC_IF_HANDLE MyInterface_v1_0_c_ifspec;
extern RPC_IF_HANDLE MyInterface_v1_0_s_ifspec;

//MyApp_s.c
RPC_IF_HANDLE MyInterface_v1_0_s_ifspec = (RPC_IF_HANDLE)& MyInterface___RpcServerInterface;

服务器程序在接口中提供同一过程的多个实现时,将使用后两个参数。

RpcServerRegisterIfEx提供了安全回调函数保护接口。

4.2.2 使服务器在网络上可用

希望在本地接收呼叫的服务器应使用ncalrpc。接受远程呼叫的服务器应使用ncacn_ip_tcp

大多数服务器程序使用网络上所有可用的协议序列。为此,它们调用RpcServerUseProtseq函数。其它还有RpcServerUseAllProtseqs,RpcServerUseProtseqEx,RpcServerUseProtseqEp或RpcServerUseProtseqEpEx

下一步,绑定一个vector指针。用完记着调用RpcBindingVectorFree

目前的代码:

long StartServer()
{
	wchar_t wszProtocolSequence[] = { L"ncacn_ip_tcp" };
	RPC_STATUS status;
	status = RpcServerRegisterIf(MyInterface_v1_0_s_ifspec, NULL, NULL);

	status = RpcServerUseProtseqW(RPC_WSTR(L"ncacn_ip_tcp"), RPC_C_PROTSEQ_MAX_REQS_DEFAULT, NULL);

	RPC_BINDING_VECTOR *rpcBindingVector;
	status = RpcServerInqBindings(&rpcBindingVector);

	RpcBindingVectorFree(&rpcBindingVector);

	return 0;
}

4.2.3 注册端点(实际无)

status = RpcEpRegister(
		MyInterface_v1_0_s_ifspec, 
		rpcBindingVector, 
		NULL, //本例没有要注册的对象UUID
		RPC_CSTR(L"note"));	//注释字符串,建议使用

4.2.4 监听

相关函数:RpcServerListen, RpcMgmtStopServerListening, RpcMgmtWaitServerListen

status = RpcServerListen(
		1,	// 最小线程数 
		RPC_C_LISTEN_MAX_CALLS_DEFAULT,	//  最大线程数
		0);	//默认DCE行为

4.2.5 代码

以上实际编译时会提示无法解析midl_user_allocate, midl _user_free,这两个函数在服务端分配和释放内存。

#include "MyApp_h.h"
//#include "../RpcTest/MyApp_h.h"
//#include "../RpcTest/MyApp_s.c"

#pragma comment(lib, "Rpcrt4.lib")

long StartServer();

int main()
{
	StartServer();

	return 0;
}

long StartServer()
{
	wchar_t wszProtocolSequence[] = { L"ncalrpc" };
	wchar_t wszEndPoint[] = { L"12888" };

	RPC_STATUS status;
	//RPC_BINDING_VECTOR *rpcBindingVector;
	do 
	{
		status = RpcServerUseProtseqEpW(
			RPC_WSTR(wszProtocolSequence),
			RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
			RPC_WSTR(wszEndPoint),
			NULL);
		if (RPC_S_OK != status)
			break;

		status = RpcServerRegisterIf(MyInterface_v1_0_s_ifspec, NULL, NULL);
		if (RPC_S_OK != status)
			break;

		
		//status = RpcServerInqBindings(&rpcBindingVector);
		//if (RPC_S_OK != status)
		//	break;


		//status = RpcEpRegisterW(
		//	MyInterface_v1_0_s_ifspec,
		//	rpcBindingVector,
		//	NULL, 
		//	NULL);	//注释字符串,建议使用
		//if (RPC_S_OK != status)
		//	break;

		status = RpcServerListen(
			1,	// 最小线程数 
			RPC_C_LISTEN_MAX_CALLS_DEFAULT,	//  最大线程数
			0);	//默认DCE行为	
		if (RPC_S_OK != status)
			break;

	} while (0);
	if (RPC_S_OK != status)
		exit(status);

	//RpcBindingVectorFree(&rpcBindingVector);

	return 0;
}

void MyRemoteProc(
	/*[in]*/ int param1,
	/*[out]*/ int *outParam)
{
	*outParam = param1 + 1;
}


void __RPC_FAR * __RPC_USER midl_user_allocate(size_t cBytes)
{
	return((void __RPC_FAR *) malloc(cBytes));
}

void __RPC_USER midl_user_free(void __RPC_FAR * p)
{
	free(p);
}

4.3 客户端如何建立连接

RPC overviews-1_第8张图片

4.3.1 创建绑定句柄

分布式应用程序的客户端程序需要创建一个绑定句柄,该句柄告诉RPC运行时应联系哪个服务器以及应如何联系该服务器。

unsigned short *pszUUID = NULL;
unsigned short *StringBinding;
RPC_BINDING_HANDLE bindingHandle;

RPC_STATUS status;
do
{
	status = RpcStringBindingComposeW(
		pszUUID,
		RPC_WSTR(L"ncacn_ip_tcp"),
		NULL,
		NULL,
		NULL,
		&StringBinding);
	if (RPC_S_OK != status)
		break;

	status = RpcBindingFromStringBindingW(StringBinding, &bindingHandle);
	if (RPC_S_OK != status)
		break;

	RpcStringFree(&StringBinding);
} while (0);

然后在RpcTryExcept中调用就行了。

4.3.2 进行RPC

4.3.3 查找服务器程序

4.3.4 将呼叫发送到服务器程序

4.3.5 代码

注意,代码中全程没有出现uuid。

#include "MyApp_h.h"
//#include "../RpcTest/MyApp_h.h"
//#include "../RpcTest/MyApp_c.c"

#include 
#pragma comment( lib, "Rpcrt4.lib" )

long CallServer();
int main()
{
	CallServer();
	return 0;
}

long CallServer()
{
	wchar_t wszProtocolSequence[] = { L"ncalrpc" };
	wchar_t wszEndPoint[] = { L"12888" };

	wchar_t *pwszStringBinding = NULL;
	//handle_t bindingHandle = 0;

	RPC_STATUS status;
	do
	{
		status = RpcStringBindingComposeW(
			NULL,
			RPC_WSTR(wszProtocolSequence),
			NULL, //RPC_WSTR(L"127.0.0.1"),
			RPC_WSTR(wszEndPoint),
			NULL,
			(RPC_WSTR*)&pwszStringBinding);
		if (RPC_S_OK != status)
			break;

		status = RpcBindingFromStringBindingW(RPC_WSTR(pwszStringBinding), &bindingHandle);
		if (RPC_S_OK != status)
			break;


	} while (0);
	if (RPC_S_OK != status)
		exit(status);
	
	int nInt = 0;
	RpcTryExcept
	{
		MyRemoteProc(1, &nInt);
		printf("%d\n", nInt);
	}
	RpcExcept(1)
	{
		int a = RpcExceptionCode();
		printf("Rpc Exception Code: %d\n", a);
	}
	RpcEndExcept

	RpcStringFreeW((RPC_WSTR*)&pwszStringBinding);
	RpcBindingFree(&bindingHandle);
	getchar();
	return 0;
}


void __RPC_FAR * __RPC_USER midl_user_allocate(size_t cBytes)
{
	return((void __RPC_FAR *) malloc(cBytes));
}

void __RPC_USER midl_user_free(void __RPC_FAR * p)
{
	free(p);
}

你可能感兴趣的:(windows)