RPC编程(linux)

RPC

在介绍RPC之前,我们有必要先介绍一下IPC

进程间通信(IPC,Inter-Process Communication),指至少两个进程或线程间传送数据或信号的一些技术或方法。进程是计算机系统分配资源的最小单位。每个进程都有自己的一部分独立的系统资源,彼此是隔离的。为了能使不同的进程互相访问资源并进行协调工作,才有了进程间通信。这些进程可以运行在同一计算机上或网络连接的不同计算机上。 进程间通信技术包括消息传递、同步、共享内存和远程过程调用。 IPC是一种标准的Unix通信机制。

有两种类型的进程间通信(IPC):

  • 本地过程调用(LPC)LPC用在多任务操作系统中,使得同时运行的任务能互相会话。这些任务共享内存空间使任务同步和互相发送信息。
  • 远程过程调用(RPC)RPC类似于LPC,只是在网上工作。RPC开始是出现在Sun微系统公司和HP公司的运行UNIX操作系统的计算机中。

为什么要用RPC呢?

我们知道,同一主机的同一程序之间因为有函数栈的存在,使得函数的相互调用很简单。但是两个程序(程序A和程序B)分别运行在不同的主机上,A想要调用B的函数来实现某一功能,那么使用常规的方法就是不可实现的,RPC就是干这个活的

RPC的核心并不在于使用什么协议。RPC的目的是让你在本地调用远程的方法,而对你来说这个调用是透明的,你并不知道这个调用的方法是部署哪里。通过RPC能解耦服务,这才是使用RPC的真正目的。RPC的原理主要用到了动态代理模式,至于http协议,只是传输协议而已。简单的实现可以参考spring remoting,复杂的实现可以参考dubbo。

?

总结一下

  • RPC就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。
  • RPC 会隐藏底层的通讯细节(不需要直接处理Socket通讯或Http通讯) RPC 是一个请求响应模型。
  • 客户端发起请求,服务器返回响应(类似于Http的工作方式) RPC 在使用形式上像调用本地函数(或方法)一样去调用远程的函数(或方法)。

?

XDR

XDR是一个数据描述和数据编码的标准,XDR的主要作用就是在不同进程间传递消息参数时,避免因为计算机平台的不一致而导致数据传送接收异常。它可以对消息参数按照一定的顺序编码,放在一个数据包里(通常是在内存中申请一个一定大小的字符串缓冲区),然后把这个数据包发送给其他平台,然后在按照之前编码的顺序依次解码,并可以获得原来的消息参数。

?

rpcgen

rpcgen介绍

可以看执行man rpcgen在man page中查看rpcgen的介绍,不得不说man page真的是很强大的工具,里面的介绍说明言简意赅,还能练英语 ??

翻译:
rpcgen是一种工具,它可以生成实现RPC的C语言代码。使用rpcgen时,你需要提供一个与C语言类似的RPC语言源文件。
rpcgen通常通过一个源文件生成四个输出文件。如果输入文件是proto.x,rpcgen将生成一个头文件proto.h,XDR规则proto_xdr.c,服务端存根proto_svc.c,客户端存根proto_clt.c 若使用-T选项,还会生成一个proto_tbl.i,使用-Sc选项,将会生成一个客户端的rpc样例,使用-Ss选项将会生成一个服务端的rpc样例。

rpcgen命令选项

usage: rpcgen infile
    rpcgen [-abkCLNTM][-Dname[=value]] [-i size] [-I [-K seconds]] [-Y path] infile
    rpcgen [-c | -h | -l | -m | -t | -Sc | -Ss | -Sm] [-o outfile] [infile]
    rpcgen [-s nettype]* [-o outfile] [infile]
    rpcgen [-n netid]* [-o outfile] [infile]
options:
-a      generate all files, including samples
-b      backward compatibility mode (generates code for SunOS 4.1)
-c      generate XDR routines
-C      ANSI C mode
-Dname[=value]  define a symbol (same as #define)
-h      generate header file
-i size     size at which to start generating inline code
-I      generate code for inetd support in server (for SunOS 4.1)
-K seconds  server exits after K seconds of inactivity
-l      generate client side stubs
-L      server errors will be printed to syslog
-m      generate server side stubs
-M      generate MT-safe code
-n netid    generate server code that supports named netid
-N      supports multiple arguments and call-by-value
-o outfile  name of the output file
-s nettype  generate server code that supports named nettype
-Sc     generate sample client code that uses remote procedures
-Ss     generate sample server code that defines remote procedures
-Sm         generate makefile template 
-t      generate RPC dispatch table
-T      generate code to support RPC dispatch tables
-Y path     directory name to find C preprocessor (cpp)
-5      SysVr4 compatibility mode
--help      give this help list
--version   print program version

?

部分options解释:

-a 生成所有源程序,包括客户机和服务器源程序。

-C 使用ANSI C标准生成编码。

-c 生成xdr转码C程序。(file_xdr.c)。

-l 生成客户机stubs。(file_clnt.c)

-m 生成服务器stubs,但是不生成main函数。(file_svc.c)

-s  rpcgen –C –s tcp file.x,生成服务器stubs,用tcp协议,同时生成了main函数。(file_svc.c)

-h 生成头文件。

-Sc 生成骨架客户机程序,(file_client.c),生成后还需要手动添加代码。

-Ss 生成服务器程序,(file_server.c),生成后还需要手动添加代码。

?

Linux上编写rpc-demo

Linux下面的RPC模型是SUN RPC (ONC RPC),使用了XDR来编码/解码数据。gcc提供了一些标准数据类型的XDR filter(比如整型,浮点型,字符串等)。对于自定义数据类型,则需要自己编写XDR filter来处理。

你可以使用rpcgen来帮你自动生成xdr filter,但是,该工具需要你提供一个 .x 文件。

这里提供两个demo作为参考

?

  • 简易计算器

(1)编写.x文件

/* 
 * filename: calculator.x 
 * function: 定义远程调用中常量、非标准数据类型以及调用过程
 */

const ADD = 0;
const SUB = 1;
const MUL = 2;
const DIV = 3;

struct CALCULATOR
{
    int op; /* 0-ADD, 1-SUB, 2-MUL, 3-DIV */
    float arg1;
    float arg2;
    float result;
};

program CALCULATOR_PROG
{
    version CALCULATOR_VER
    {
        struct CALCULATOR CALCULATOR_PROC(struct CALCULATOR) = 1;
    } = 1;  /*  版本号=1 */
} = 0x20000001; /* RPC程序编号 */

?

(2)使用rpcgen生成文件

想一步到位,生成所有文件的话执行:

$ rpcgen -a calculator.x

(如果你需要分步执行也是可以的,根据上面写的rpcgen命令使用不同的参数)

?

生成以下文件:

文件名 作用
Makefile.calculator 该文件用于编译所有客户机,服务器代码
calculator.h 声明用到的变量和函数
calculator_xdr.c 非标准数据类型的编码
calculator_clnt.c 将远程调用代理( proxying ) 为本地OS调用,并将调用参数打包成一个消息,然后将此消息发送给服务器。由rpcgen生成,程序员一般无需修改
calculator_svc.c 将通过网络输入的请求转换为本地过程调用,即负责解压服务器上收到的消息,并调用实际的服务器端、应用程序级的实现,程序员一般不用修改
calculator_client.c 骨架客户端程序,需要自行修改
calculator_server.c 骨架服务端程序,需要自行修改

?

(3)修改骨架程序

这里需要我们自己修改calculator_client.c和calculator_server.c来实现功能

注:/* -<<< Add to test*/ 包含的为添加的代码

?

calculator_client.c

/*
 * This is sample code generated by rpcgen.
 * These are only templates and you can use them
 * as a guideline for developing your own functions.
 */

#include "calculator.h"


void
calculator_prog_1(char *host)
{
	CLIENT *clnt;
	struct CALCULATOR  *result_1;
	struct CALCULATOR  calculator_proc_1_arg;

#ifndef	DEBUG
	clnt = clnt_create (host, CALCULATOR_PROG, CALCULATOR_VER, "udp");
	if (clnt == NULL) {
		clnt_pcreateerror (host);
		exit (1);
	}
#endif	/* DEBUG */

	/* -<<< Add to test*/
    char c;

    printf("choose the operation:\n\t0---ADD\n\t1---SUB\n\t2---MUL\n\t3---DIV\n");
    c = getchar();

	if(c>‘3‘ && c<‘0‘){
		printf("error:operate/n");
		exit(1);
	}
    calculator_proc_1_arg.op = c-‘0‘;

    printf("input the first number:");
    scanf("%f", &calculator_proc_1_arg.arg1);

    printf("input the second number:");
    scanf("%f", &calculator_proc_1_arg.arg2);

    /* -<<< Add to test*/

	result_1 = calculator_proc_1(&calculator_proc_1_arg, clnt);
	if (result_1 == (struct CALCULATOR *) NULL) {
		clnt_perror (clnt, "call failed");
	}
#ifndef	DEBUG
	clnt_destroy (clnt);
#endif	 /* DEBUG */

    /* -<<< Add to test*/
    printf("The Result is %.3f \n", result_1->result);
    /* -<<< Add to test*/
}


int
main (int argc, char *argv[])
{
	char *host;

	if (argc < 2) {
		printf ("usage: %s server_host\n", argv[0]);
		exit (1);
	}
	host = argv[1];
	calculator_prog_1 (host);
exit (0);
}

?

calculator_server.c

/*
 * This is sample code generated by rpcgen.
 * These are only templates and you can use them
 * as a guideline for developing your own functions.
 */

#include "calculator.h"

struct CALCULATOR *
calculator_proc_1_svc(struct CALCULATOR *argp, struct svc_req *rqstp)
{
	static struct CALCULATOR  result;

	/*
	 * insert server code here
	 */
 	/* -<<< Add to test*/
    switch(argp->op){
		case ADD:
            result.result = argp->arg1 + argp->arg2;
            break;
        case SUB:
            result.result = argp->arg1 - argp->arg2;
            break;
        case MUL:
            result.result = argp->arg1 * argp->arg2;
            break;
        case DIV:
            result.result = argp->arg1 / argp->arg2;
            break;
        default:
            break;

    }
    /* -<<< Add to test*/
	return &result;
}

?

(4)编译

rpcgen帮我们生成了makefile,使用make -f命令调用即可

$ make -f Makefile.calculator 

?

(5)执行

RPC编程(linux)

?

  • 服务端时间获取

(1)编写.x文件

program DATE_PROG { 
    version DATE_VERS { 
        long GET_DATE(void) = 1; 
    } = 1; 
} = 0x20000002; 

(2)使用rpcgen生成文件

$ rpcgen -a date.x

(3)修改骨架程序

date_client.c

/*
 * This is sample code generated by rpcgen.
 * These are only templates and you can use them
 * as a guideline for developing your own functions.
 */

#include "date.h"


void
date_prog_1(char *host)
{
	CLIENT *clnt;
	long  *result_1;
	char *get_date_1_arg;

#ifndef	DEBUG
	clnt = clnt_create (host, DATE_PROG, DATE_VERS, "udp");
	if (clnt == NULL) {
		clnt_pcreateerror (host);
		exit (1);
	}
#endif	/* DEBUG */

	result_1 = get_date_1((void*)&get_date_1_arg, clnt);
	if (result_1 == (long *) NULL) {
		clnt_perror (clnt, "call failed");
	}

 	/* -<<< Add to test*/
	printf("%ld\n",*result_1);
 	/* -<<< Add to test*/
	 
#ifndef	DEBUG
	clnt_destroy (clnt);
#endif	 /* DEBUG */
}


int
main (int argc, char *argv[])
{
	char *host;

	if (argc < 2) {
		printf ("usage: %s server_host\n", argv[0]);
		exit (1);
	}
	host = argv[1];
	date_prog_1 (host);
exit (0);
}

?

date_server.c

/*
 * This is sample code generated by rpcgen.
 * These are only templates and you can use them
 * as a guideline for developing your own functions.
 */

#include "date.h"
#include <time.h>

long *
get_date_1_svc(void *argp, struct svc_req *rqstp)
{
	static long  result;

	/*
	 * insert server code here
	 */

 	/* -<<< Add to test*/
	result = (long)time(0);
 	/* -<<< Add to test*/
	return &result;
}

(4)编译(同上一个例子)

(5)执行

RPC编程(linux)

?

出现的问题

  • 服务器无法启动,错误如下:Cannot register service: RPC: Unable to receive; errno = Connection refused
    unable to register (TESTPROG, VERSION, udp).

    问题原因:系统没有安装 portmap 或者没有启动 portmap 端口映射。

    解决方法: 在新版的 ubuntu 中 portmap 被 rpcbind 所代替, 所以需要启动 rpcbind 服务。

    service rpcbind restart
    

?

参考资料

https://www.jianshu.com/p/b0343bfd216e
https://www.jianshu.com/p/b33fc01c1f59
http://blog.chinaunix.net/uid-20644632-id-2220585.html
https://blog.csdn.net/weixin_30538029/article/details/95014067

RPC编程(linux)

原文:https://www.cnblogs.com/Irvingcode/p/12887671.html

上一篇:UBUNTU 16.04 虚拟机上网问题汇总


下一篇:go httprouter