zoukankan      html  css  js  c++  java
  • 关于rpc简介、原理、实例缘于difx

    关于rpc简介、原理、实例-缘于difx

           今天稍微研究了一下关于rpc的东东,主要是因为Pro.Z碰到的问题,在解决了以后,对rpc产生了一些兴趣,考虑到我目前的程序也可以从比较底层的socket方式概率为rpc方式,看的过程中,看到了一个网上的实例教程,估计已经转载了N遍了,所以有几个错误,包括rpc unknown protocalconfliction definecannot register service等,在测试的时候也进行了一些改正,并且进行了测试。

           单单是这个小程序,就可以做一个时间服务器性质的程序了。

    简介

    在传统的编程概念中,过程是由程序员在本地编译完成,并只能局限在本地运行的一段代码,也即其主程序和过程之间的运行关系是本地调用关系。因此这种结构在网络日益发展的今天已无法适应实际需求。总所周知,传统过程调用模式无法充分利用网络上其他主机的资源(如CPUMemory等),也无法提高代码在实体间的共享程度,使得主机资源大量浪费。

          通过RPC我们可以充分利用非共享内存的多处理器环境(例如通过局域网连接得多台工作站),这样可以简便地将你的应用分布在多台工作站上,应用程序就像运行在一个多处理器的计算机上一样。你可以方便的实现过程代码共享,提高系统资源的利用率,也可以将以大量数值处理的操作放在处理能力较强的系统上运行,从而减轻前端机的负担。

    RPC的结构原理及其调用机制

    如前所述RPC其实也是种C/S的编程模式,有点类似C/S Socket 编程模式,但要比它

    更高一层。当我们在建立RPC服务以后,客户端的调用参数通过底层的RPC传输通道,可以是UDP,也可以是TCP(也即TI-RPC—无关性传输),并根据传输前所提供的目的地址及RPC上层应用程序号转至相应的RPC应用程序服务端,且此时的客户端处于等待状态,直至收到应答或Time Out超时信号。具体的流程图如F1。当服务器端获得请求消息,则会根据注册RPC时告诉RPC系统的例程入口地址,执行相应的操作,并将结果返回至客户端。

     
      clip_image002

    F1

    当一次RPC调用结束后,相应线程发送相应的信号,客户端程序才会继续运行。

    在这个过程中,一个远程过程是有三个要素来唯一确定的:程序号、版本号和过程号。

    程序号是用来区别一组相关的并且具有唯一过程好的远程过程。一个程序可以有一个或几个不同的版本,而每个版本的程序都包含一系列能被远程调用的过程,通过版本的引入,使得不同版本下的RPC能同时提供服务。每个版本都包含有许多可供远程调用的过程,每个过程则有其唯一标示的过程号。

    基于RPC的应用系统开发

    通过以上对RPC原理的简介后,我们再来继续讨论如何来开发基于RPC的应用系统。

    一般而言在开发RPC时,我们通常分为三个步骤:

    a、 定义说明客户/服务器的通信协议:这里所说的通信协议是指定义服务过程的名称、调用参数的数据类型和返回参数的数据类型,还包括底层传输类型(可以是UDPTCP),当然也可以由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( )

    通过调用rpcbindRPC服务和网络地址做映射

    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协议编译工具—RpcgenRpcgen 工具用来生成远程程序接口模块,它将以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:

    /* time.x: Remote time printing protocol */

    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\n", *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 message\n", 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 time\n",argv[0]);

    exit(1);

    }

    printf("From the Time Server ...%s\n",result);

    clnt_destroy( clnt );

    exit(0);

    }

     

    编译方法

       有了以上的三段代码后,就可用rpcgen 编译工具进行RPC协议编译,命令如下:

       $rpcgen time.x

       rpcgen 会自动生成time.htime_svc.ctime_clnt.c

       再用系统提供的gcc进行C的编译,命令如下:

       $gcc rtime.c time_clnt.c -o rtime –lnsl                            //客户端编译

    $gcc time_proc.c time_svc.c -o time_server  –lnsl     error//服务器端编译

    编译成功后即可在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账户即可

    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。

  • 相关阅读:
    phpMyAdmin 尝试连接到 MySQL 服务器,但服务器拒绝连接 解决办法
    MySQL意外关闭, 导致软件崩溃而无法启动的解决办法
    !function 笔记
    C++中四种类型转换方式
    SpringCloud面试题及答案
    Spring Boot面试题
    javaWeb常用面试题
    mysql行转列 问题 SUM(IF(条件,列值,0))
    mysql行列转换
    C++面试常见题
  • 原文地址:https://www.cnblogs.com/shaoguangleo/p/2806116.html
Copyright © 2011-2022 走看看