rpc简介、原理、实例
简介
RPC(Remote Procedure Call,远程过程调用)是建立在Socket之上的,出于一种类比的愿望,在一台机器上运行的主程序,可以调用另一台机器上准备好的子程序,就像LPC(本地过程调用).越底层,代码越复杂、灵活性越高、效率越高;越上层,抽象封装的越好、代码越简单、效率越差。Socket和RPC的区别再次说明了这点。在传统的编程概念中,过程是由程序员在本地编译完成,并只能局限在本地运行的一段代码,也即其主程序和过程之间的运行关系是本地调用关系。因此这种结构在网络日益发展的今天已无法适应实际需求。众所周知,传统过程调用模式无法充分利用网络上其他主机的资源(如CPU、Memory等),也无法提高代码在实体间的共享程度,使得主机资源大量浪费。
通过RPC我们可以充分利用非共享内存的多处理器环境(例如通过局域网连接得多台工作站),这样可以简便地将你的应用分布在多台工作站上,应用程序就像运行在一个多处理器的计算机上一样。你可以方便的实现过程代码共享,提高系统资源的利用率,也可以将以大量数值处理的操作放在处理能力较强的系统上运行,从而减轻前端机的负担。
RPC作为普遍的C/S开发方法,开发效率高效,可靠.但RPC方法的基本原则是--以模块调用的简单性忽略通讯的具体细节,以便程序员不用关心C/S之间的通讯协议,集中精力对付实现过程.这就决定了 RPC生成的通讯包不可能对每种应用都有最恰当的处理办法,与Socket方法相比,传输相同的有效数据,RPC占用更多的网络带宽.
RPC是在Socket的基础上实现的,它比socket需要更多的网络和系统资源.另外,在对程序优化时,程序员虽然可以直接修改由rpcgen产生的令人费解的源程序,但对于追求程序设计高效率的RPC而言,获得的简单性则被大大削弱.
RPC的结构原理及其调用机制
如前所述RPC其实也是种C/S的编程模式,有点类似C/S Socket 编程模式,但要比它更高一层。当我们在建立RPC服务以后,客户端的调用参数通过底层的RPC传输通道,可以是UDP,也可以是TCP(也即TI-RPC—无关性传输),并根据传输前所提供的目的地址及RPC上层应用程序号转至相应的RPC应用程序服务端,且此时的客户端处于等待状态,直至收到应答或Time Out超时信号。当服务器端获得请求消息,则会根据注册RPC时告诉RPC系统的例程入口地址,执行相应的操作,并将结果返回至客户端。
当一次RPC调用结束后,相应线程发送相应的信号,客户端程序才会继续运行。
在这个过程中,一个远程过程是有三个要素来唯一确定的:程序号、版本号和过程号。
程序号是用来区别一组相关的并且具有唯一过程好的远程过程。一个程序可以有一个或几个不同的版本,而每个版本的程序都包含一系列能被远程调用的过程,通过版本的引入,使得不同版本下的RPC能同时提供服务。每个版本都包含有许多可供远程调用的过程,每个过程则有其唯一标示的过程号。
基于RPC的应用系统开发
通过以上对RPC原理的简介后,我们再来继续讨论如何来开发基于RPC的应用系统。
一般而言在开发RPC时,我们通常分为三个步骤:
a、 定义说明客户/服务器的通信协议:这里所说的通信协议是指定义服务过程的名称、调用参数的数据类型和返回参数的数据类型,还包括底层传输类型(可以是UDP或TCP),当然也可以由RPC底层函数自动选择连接类型建立TI-RPC。最简单的协议生成的方法是采用协议编译工具,常用的有Rpcgen,我会在后面实例中详细描述其使用方法。
b、 开发客户端程序。
c、 开发服务器端程序。
开发客户端和服务器端的程序时,RPC提供了我们不同层次的开发例程调用接口。不同层次的接口提供了对RPC不同程度控制。一般可分为5个等级的编程接口,接下来我们分别讨论一下各层所提供的功能函数。
简单层例程
简单层是面向普通RPC应用,为了快速开发RPC应用服务而设计的,他提供了如下功能函数。
|
函数名 |
功能描述 |
|
Rpc_reg( ) |
在一特定类型的传输层上注册某个过程,来作为提供服务的RPC程序 |
|
Rpc_call( ) |
远程调用在指定主机上指定的过程 |
|
Rpc_Broadcast( ) |
向指定类型的所有传输端口上广播一个远程过程调用请求 |
高层例程
在这一层,程序需要在发出调用请求前先创建一个客户端句柄,或是在侦听请求前先建立一个服务器端句柄。程序在该层可以自由的将自己的应用绑在所有的传输端口上,它提供了如下功能函数。
|
函数名 |
功能描述 |
|
Clnt_create( ) |
程序通过这个功能调用,告诉底层RPC服务器的位置及其传输类型 |
|
Clnt_create_timed( ) |
定义每次尝试连接的超时最大时间 |
|
Svc_create( ) |
在指定类型的传输端口上建立服务器句柄,告诉底层RPC事件过程的相应入口地址 |
|
Clnt_call() |
向服务器端发出一个RPC调用请求 |
中间层例程
中间层向程序提供更为详细的RPC控制接口,而这一层的代码变得更为复杂,但运行也更为有效,它提供了如下功能函数。
|
函数名 |
功能描述 |
|
Clnt_tp_create( ) |
在指定的传输端口上建立客户端句柄 |
|
Clnt_tp_create_timed( ) |
定义最大传输时延 |
|
Svc_tp_creaet( ) |
在指定的传输端口上建立服务句柄 |
|
Clnt_call( ) |
向服务器端发出RPC调用请求 |
专家层例程
这层提供了更多的一系列与传输相关的功能调用,它提供了如下功能函数。
|
函数名 |
功能描述 |
|
Clnt_tli_create( ) |
在指定的传输端口上建立客户端句柄 |
|
Svc_tli_create( ) |
在指定的传输端口上建立服务句柄 |
|
Rpcb_set( ) |
通过调用rpcbind将RPC服务和网络地址做映射 |
|
Rpcb_unset( ) |
删除rpcb_set( ) 所建的映射关系 |
|
Rpcb_getaddr( ) |
调用rpcbind来犯会指定RPC服务所对应的传输地址 |
|
Svc_reg( ) |
将指定的程序和版本号与相应的时间例程建起关联 |
|
Svc_ureg( ) |
删除有svc_reg( ) 所建的关联 |
|
Clnt_call( ) |
客户端向指定的服务器端发起RPC请求 |
底层例程
该层提供了所有对传输选项进行控制的调用接口,它提供了如下功能函数。
|
函数名 |
功能描述 |
|
Clnt_dg_create( ) |
采用无连接方式向远程过程在客户端建立客户句柄 |
|
Svc_dg_create( ) |
采用无连接方式建立服务句柄 |
|
Clnt_vc_create( ) |
采用面向连接的方式建立客户句柄 |
|
Svc_vc_create( ) |
采用面向连接的方式建立RPC服务句柄 |
|
Clnt_call( ) |
客户端向服务器端发送调用请求 |
实例介绍
以下我将通过实例向读者介绍通过简单层RPC的实现方法。通常在此过程中我们将使用RPC协议编译工具—Rpcgen。Rpcgen 工具用来生成远程程序接口模块,它将以RPC语言书写的源代码进行编译,Rpc 语言在结构和语法上同C语言相似。由Rpcgen 编译生成的C源程序可以直接用C编译器进行编译,因此整个编译工作将分为两个部分。Rpcgen的源程序以.x结尾,通过其编译将生成如下文件:
a) 一个头文件(.h)包括服务器和客户端程序变量、常量、类型等说明。
b) 一系列的XDR例程,它可以对头文件中定义的数据类型进行处理。
c) 一个Server 端的标准程序框架。
d) 一个Client 端的标准程序框架。
当然,这些输出可以是选择性的,Rpcgen 的编译选项说明如下:
|
选项 |
功能 |
|
‘-’ a |
生成所有的模板文件 |
|
‘-’ Sc |
生成客户端的模板文件 |
|
‘-’ Ss |
生成服务器端的模板文件 |
|
‘-’ Sm |
生成Makefile 文件 |
(详见Solaris Rpcgen Manaul)
Rpcgen 源程序 time.x:
- program TIMEPROG {
- version PRINTIMEVERS {
- string PRINTIME(string) = 1; /* 过程号 */
- } = 1; /* 版本号 */
- } = 0x20000001; /* 程序号 */
time_proc.c源程序:
- /* time_proc.c: implementation of the remote procedure "printime" */
- #include <stdio.h>
- #include <rpc/rpc.h> /* always needed */
- #include "time.h" /* time.h will be generated by rpcgen */
- #include <time.h>
- /* Remote version of "printime" */
- char ** printime_1_svc(char **msg,struct svc_req *req)
- {
- static char * result; /* must be static! */
- static char tmp_char[100];
- time_t rawtime;
- FILE *f;
- f = fopen("/tmp/rpc_result", "a+");
- if (f == (FILE *)NULL) {
- strcpy(tmp_char,"Error");
- result = tmp_char;;
- return (&result);
- }
- fprintf(f, "%s", *msg); //used for debugging
- fclose(f);
- time(&rawtime);
- sprintf(tmp_char,"Current time is :%s",ctime(&rawtime));
- result =tmp_char;
- return (&result);
- }
rtime.c源代码
- /*
- * rtime.c: remote version
- * of "printime.c"
- */
- #include <stdio.h>
- #include "time.h" /* time.h generated by rpcgen */
- main(int argc, char **argv)
- {
- CLIENT *clnt;
- char *result;
- char *server;
- char *message;
- if (argc != 3) {
- fprintf(stderr, "usage: %s host messagen", argv[0]);
- exit(1);
- }
- server = argv[1];
- message = argv[2];
- /*
- * Create client "handle" used for
- * calling TIMEPROG on the server
- * designated on the command line.
- */
- //clnt = clnt_create(server, TIMEPROG, PRINTIMEVERS, "visible");
- clnt = clnt_create(server, TIMEPROG, PRINTIMEVERS, "TCP");
- if (clnt == (CLIENT *)NULL) {
- /*
- * Couldn't establish connection
- * with server.
- * Print error message and die.
- */
- clnt_pcreateerror(server);
- exit(1);
- }
- /*
- * Call the remote procedure
- * "printime" on the server
- */
- result =*printime_1(&message,clnt);
- if (result== (char *)NULL) {
- /*
- * An error occurred while calling
- * the server.
- * Print error message and die.
- */
- clnt_perror(clnt, server);
- exit(1);
- }
- /* Okay, we successfully called
- * the remote procedure.
- */
- if (strcmp(result,"Error")==0){
- /*
- * Server was unable to print
- * the time.
- * Print error message and die.
- */
- fprintf(stderr, "%s: could not get the timen",argv[0]);
- exit(1);
- }
- printf("From the Time Server ...%s",result);
- clnt_destroy( clnt );
- exit(0);
- }
编译方法
有了以上的三段代码后,就可用rpcgen 编译工具进行RPC协议编译,命令如下:
$rpcgen time.x(rpcgen -C -a -M -N time.x(多个参数时))
rpcgen 会自动生成time.h、time_svc.c、time_clnt.c
再用系统提供的gcc进行C的编译,命令如下:
$gcc rtime.c time_clnt.c -o rtime –lnsl //客户端编译
$gcc time_proc.c time_svc.c -o time_server –lnsl //服务器端编译
编译成功后即可在Server端运行time_server,立即将该服务绑定在rpc服务端口上提供服务。在客户端运行./rdate hostname msg (msg 是一字符串,笔者用来测试时建立的),立即会返回hostname 端的时间。
出错问题
Cannot register service
RPC: Authentication error; why = Client credential too weak unable to register (TIMEPROG, PRINTIMEVERS, udp)
解决方法:使用root账户即可(同时检查portmap是否安装了)
time_proc.c:7: error: conflicting types for printime_1?
源程序time_proc.c中报错
解决方法,改为char ** printime_1_svc(char **msg,struct svc_req *req)即可。
RPC: Unknown protocol
这个原因可能是rpc依赖包没有安装,或者使用函数clnt_create的时候,最后一个参数应该不使用visible,而是tcp。
参考文档:http://docs.freebsd.org/44doc/psd/22.rpcgen/paper.pdf
Linux.C 高级程序员指南
http://www.cs.rutgers.edu/~pxk/rutgers/notes/rpc/index.html
http://fossies.org/linux/misc/old/portmap-6.0.tgz/
rpc简介、原理、实例的更多相关文章
- 01 . RPC简介原理及用Go实现一个简单的RCP
RPC简介 本地过程调用 // 正常情况下程序的执行和调用情况.例如有如下go语言代码: package main import "fmt" func main() { var a ...
- 网络编程 -- RPC实现原理 -- RPC -- 迭代版本V3 -- 远程方法调用 整合 Spring
网络编程 -- RPC实现原理 -- 目录 啦啦啦 V3——RPC -- 远程方法调用 及 null的传输 + Spring 服务提供商: 1. 配置 rpc03_server.xml 注入 服务提供 ...
- 网络编程 -- RPC实现原理 -- RPC -- 迭代版本V4 -- 远程方法调用 整合 Spring 自动注册
网络编程 -- RPC实现原理 -- 目录 啦啦啦 V4——RPC -- 远程方法调用 + Spring 自动注册 服务提供商: 1. 配置 rpc04_server.xml 注入 服务提供商 rpc ...
- Hadoop的RPC工作原理
RPC远程过程调用: Hadoop的远程过程调用(Remote Procedure Call,RPC)是Hadoop中核心通信机制,RPC主要通过所有Hadoop的组件元数据交换,如MapReduce ...
- LeakCanary 内存泄漏 监测 性能优化 简介 原理 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- RxJava RxPermissions 动态权限 简介 原理 案例 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- RPC实现原理(HSF、dubbo) 从头开始(一)
前言 阔别了很久博客园,虽然看了以前写的很多东西感觉好幼稚,但是还是觉得应该把一些自己觉得有用的东西和大家分享.废话不多说,现在开始进入正题. 之前的六年工作经验,呆过了一些大公司,每个在大公司呆过的 ...
- spring原理案例-基本项目搭建 03 创建工程运行测试 spring ioc原理实例示例
下面开始项目的搭建 使用 Java EE - Eclipse 新建一 Dynamic Web Project Target Runtime 选 Apache Tomcat 7.0(不要选 Apache ...
- 网络编程 -- RPC实现原理 -- 目录
-- 啦啦啦 -- 网络编程 -- RPC实现原理 -- NIO单线程 网络编程 -- RPC实现原理 -- NIO多线程 -- 迭代版本V1 网络编程 -- RPC实现原理 -- NIO多线程 -- ...
- 网络编程 -- RPC实现原理 -- NIO单线程
网络编程 -- RPC实现原理 -- 目录 啦啦啦 Class : Service package lime.pri.limeNio.optimize.socket; import java.io.B ...
随机推荐
- vue diff算法 patch
1.diff比较算法 图示: diff比较只会在同层级进行, 不会跨层级比较. 所以diff是:广度优先算法. 时间复杂度:O(n) 代码示例: <!-- 之前 --> <div&g ...
- osx下查看jar文件
jar是java class的打包文件,我们能够将自己的项目打包为jar文件执行,也能够打包后当做第三方包查看,有时候我们须要查看一下一个jar文件里是否还有某个类以及对应的包,我们能够採用下面两种方 ...
- maven 配置 Java Servlet API
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --><dependency> ...
- 彻底抛弃脚本录制,LR脚本之使用web_custom_request函数自定义http请求
初学性能测试时候,第一步必学脚本录制,但一路下来各种录制失败.回放脚本失败的问题层出不穷,究其原因一是LR本身存在对测试环境的兼容性问题导致录制失败,更深层次的原因是录制者不清楚LR录制脚本的原理,或 ...
- TP3.2实例化复杂模型类
1.表名:xxf_witkey_member_oauth M方法,直接实例化对象:M('member_oauth','xxf_witkey_'[,'db_config']); 具体解析:M方法三个参数 ...
- springmvc异常统一处理
http://www.cnblogs.com/xd502djj/archive/2012/09/24/2700490.html
- 【转】ubuntu如何修改/添加/删除硬盘分区的挂载(点)?
我(macroliu)的问题:安装ubuntu时,/home单独挂载在一个分区,此后我想调整分区大小,删除了/home对应的分区以及另外几个分区,导致开机时找不到挂载点.把硬盘空闲空间分好区后,想把1 ...
- this.class.getClassLoader().getResourceAsStream与this.class.getResourceAsStream
本文部分转自:http://xixinfei.iteye.com/blog/1256291 this.getClass().getClassLoader().getResource("tem ...
- Python程序数据溢出问题或出现 NAN 问题
[数据溢出问题] overflow:溢出 overflow:上溢 underflow:下溢 数据溢出包括上溢和下溢. 上溢可以理解为:你想用一个int类型来保存一个非常非常大的数,而这个超出了int类 ...
- SYS_R12 MOAC多组织底层技术实现技术分析(Oracle VPD) (案例)
2014-05-30 Created By BaoXinjian