简单的RPC java实现

1590阅读 0评论2015-11-16 sometimeixpub
分类:Java

我也承认,RPC的名声大噪之时是在2003年,那一个“冲击波”病毒(Blaster Worm virus)袭卷全球的一年。而“冲击波”正是用着RPC这把刀来敲开了远程电脑的大门。当然RPC 有更多正面的应用,比如NFSWeb Service等等。

一、RPC的介绍

  什么是RPCRemote Procedure Call,远程过程调用。也就是说,调用过程代码并不是在调用者本地运行,而是要实现调用者与被调用者二地之间的连接与通信。比较严格的定义是:Remote procedure call (RPC) is a that allows a  running on one computer to cause a  on another computer to be executed without the programmer explicitly coding the details for this interaction. When the software in question is written using  principles, RPC may be referred to as remote invocation orremote method invocation. 这样一讲,容易联想到C/S模式的程序设计,我想是对的。RPC的基本通信模型是基于Client/Server进程间相互通信模型的一种同步通信形式;它对Client提供了远程服务的过程抽象,其底层消息传递操作对Client是透明的。在RPC中,Client即是请求服务的调用者(Caller),而Server则是执行Client的请求而被调用的程序 (Callee)

  下图是RPC调用协议图:

  
   有很多文章对这张经典的图作了很好的描述,归纳讲即是:首先是建立RPC服务,约定底层的RPC传输通道(UDP或是TCP)。客户端的调用参数根据传输前所提供的目的地址及RPC 上层应用程序号,通过底层的RPC传输通道转至相应的服务器,即RPC Application Porgramme Server。客户端随即处于等待状态,以服务器等待应答或Time Out超时信号。当服务器端获得了请求消息,会根据注册RPC时告诉RPC系统的程序入口地址执行相应的操作,并将结果返回至客户端。当一次RPC调用结束后,相应线程发送相应的信号,客户端程序便继续运行。有三个要素来标识唯一的远程过程:程序号、版本号、过程号。其中,程序号是用来区别一组相关的并且具有唯一过程号的远程过程;一个程序可以有一个或几个不同的版本;而每个版本的程序都包含一系列能被远程调用的过程。(这句比较拗口难读的话,一会儿用代码来解释)同一个版本可以包含有许多可供远程调用的过程,每个过程则有其唯一标示的过程号。通过版本的引入,使得不同版本下的 RPC能同时提供服务。

  至于更深入的RPC知识,就超出了文本讨论的东西了,在这里我们主要还是来谈以Java实现的问题。


二、RPC应用开发步骤

  由我们上面对于RPC调用协议图的讲解看来,RPC的开发一般涉及三方面:
  1.定义客户端、服务器端的通信协议。此处的通信协议是指定义服务过程的名称、调用参数的数据类型、返回参数的数据类型、底层传输类型(UDP/TCP)等等。

  2.开发客户端程序。

  3.开发服务器端程序。

对于RPC通信协议的生成,最简单的方法是利用协议编译工具。常用的是rpcgen,不过这是一个用于生成实现RPC协议的C程序的生成器。要使用Java来实现的话,我们需要使用另外的生成器,即是下面要讲的Remotetea


三、工具介绍

  说起Remotetea可能有很多朋友都不太熟悉,因为我在网上搜寻关于Remotetea的中文资料一篇也没有。既然如此,我就略为写几笔吧:)

  Remotetea是一个基于GNULGPL的开源的项目,它完全在Java 2/1.1平台上实现了ONC/RPC协议;由于是纯的100%Java编写,所以不需要任何本地的库(native binary/libraries)

简单的讲,它就是今天我们用于代替rpcgen而开发纯JavaRPC应用的工具。它的特点是:

  1. 100%的纯Java开发

  2. 完整的客户端功能,包括portmapper的访问。

  3. 完整的服务器端功能。

  4. 有为.x文件设计的纯Java协议编译工具,与rpcgen兼容。

  5. 基于Javaportmapper

  6. 开源的代码;文档支持。

下载Remotetea,请在这里察看。下载bin包并解压,可以在classes文件夹中找到jrpcgen.jaroncrpc.jarportmap.jar


四、简单层RPC应用的Java实现

  1. RPC的不同层次接口

  其实在开发客户端和服务器端的程序时,RPC提供了不同层次的开发例程调用接口。不同层次的接口提供了对RPC不同程度级别的控制。一般可分为五个等级的编程接口:简单层例程、高层例程、中间层例程、专家层例程、底层例程。其中,简单层是为快速开发RPC应用服务而设计的,面向普通RPC应用;关于其他层例程,在这里就暂不提及了。简单层其函数列表如下:

  Rpc_reg( )——在某一特定类型的传输层上注册一个过程,以作为提供服务的RPC程序。

  Rpc_call( )——可以远程调用特定主机上的特定过程。

  Rpc_Broadcast( ) ——向指定类型的所有传输端口上广播一个远程过程调用请求。

实现简单层时,便会用到我们刚才要提到的Remotetea。它可以将以类C语言语法的RPC语言进行源代码编译。在这里先提一下所谓“类C语言语法的RPC语言”。

  2. RPC语言及其编译

  RPC语言是XDR语言的控制扩展,与XDR语言一样在RFC1014中定义。句法的注意事项:

  a. 有两个保留字:“program”和“version”

  b. 一个程序定义中不能出现两次版本名或版本号。

  c. 在一个版本的定义中,过程名称至多只能出现一次。

  d. 程序标识与常量和类型标识在同一空间中。

  e. 只有无符号常数才能被附值给程序,版本和过程。

所用到的文件后缀名为.x,可以称为x-文件。下面即是测试用的一个test.x文件的代码:

//////////////////////////////////////////////////////////

/*

* test.x: TEST Remote Procedure Function

*/



const MAXNAMELEN = 2048; /* maximum length of a test string */

typedef string test_string; /* a directory entry */



/*

* THE TEST Remote Procedure Function program definition

*/



program TEST_RPC_FUNCTION_NUMBER


  version TEST_RPC_FUNCTION_VERSION

  { 
            mcps_string TEST_TEST(string) = 1; /* 
这是过程号 */

            mcps_string TEST_DO_PROCESS(string) = 2; /* 这是过程号 */

        } = 1; /* 这是程序号 */

} = 0x20000001; /* 这是版本号 */


//////////////////////////////////////////////////////////

有这个文件以后,便可以在控制台敲入:java -jar jrpcgen test.x,执行后则会生成这几个文件(jrpcgen可以支持参数编译,请参照Remotetea的文档):testrpcClient.javatestrpc.javatestrpcServerStub.javatest_string.java

  3. 生成文件说明

  通过用jrpcgen编译.x文件,将生成四个java文件,下面看看每个文件是干什么的。

  testrpc.java:这个文件相当是c中的.h头文件,它主要包括服务器和客户端程序变量、常量、类型等说明。

  test_string.java:从名字可以看出是字符串变量相关的,我想应该也可以这么讲吧。它其实是一个XDR例程,可以对在testrpc.java文件中定义的数据类型进行处理。

  testrpcClient.java:客户端的标准程序框架,提供一组特定的在x-文件中定义的远程过程。该框架类继承自OncRpcClientStub类:这是一个抽象类,用于在特定的客户端上构建ONC/RPC程序的基础类。

  testrpcServerStub.java:服务器端的标准程序框架,提供一组特定的在x-文件中定义的远程过程。该框架类继承自OncRpcServerStub类并实现OncRpcDispatchable接口:前者也是一个抽象类,用于在特定的服务器端上构建ONC/RPC程序的基础类;后者接口用于分配和处理来自客户端的ONC/RPC请求。


  4. 开发客户端程序

 有了以上的介绍,形势就开始明朗了。我们的客户端程序只需继承自生成的testrpcClient这个客户端框架类就可以了。代码如下:

//////////////////////////////////////////////////////////

import java.io.IOException;

import java.net.InetAddress;

import java.net.UnknownHostException;

import org.acplt.oncrpc.OncRpcClient;

import org.acplt.oncrpc.OncRpcException;

import org.acplt.oncrpc.OncRpcProtocols;

import testrpcClient ;


/*

* @author Noshoeman

*/

public class TestClient extends testrpcClient {

//可以有很多种构造函数,有较大灵活性,这里只写一种。

    /**

    * @param host

    * @param port

    * @param protocol

    * @throws OncRpcException

    * @throws IOException   

    */

    public TestClient(InetAddress host, int port, int protocol)

        throws OncRpcException, IOException {

        super(host, port, protocol);

        //不需要做任何事。

   }

    /**

    * @param args

    * 这里是测试用的主函数

    */

    public static void main(String[] args) {

        //我们在单机测试,取得本地信息

        System.out.println("--Start client.--");

        InetAddress address = null;

        try {

            address = InetAddress.getLocalHost();

        } catch (UnknownHostException e) {

            e.printStackTrace();

        }

        //构造客户端并进行测试

        try {

            TestClient client = new TestClient(address,2023,OncRpcProtocols.ONCRPC_TCP);

            client.TEST_DO_PROCESS ("Hello!");

            client.close();

        } catch (OncRpcException e) {

            e.printStackTrace();

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

}

//////////////////////////////////////////////////////////

 

  整个过程没有什么需要多提的;注意的是在new客端的时候,端口2023是我随便写的,最后的OncRpcProtocols.ONCRPC_TCP,即是我们在前面的提到的约定底层的RPC传输通道,这里我们使用的是TCP(数值好像是6),也可以换为UDP。这样,一个简单的客户端测试程序就写好了。


  5. 开发服务器端程序

  和客户端程序的开发经验类似,也是继承自 testrpcServerStub 这个框架类。与客户端不同的是,在这里我们就需要实现远程过程的响应。示例代码如下:

//////////////////////////////////////////////////////////

import java.net.UnknownHostException;

import java.io.IOException;

import java.net.InetAddress;

import org.acplt.oncrpc.OncRpcException;

import test_string;

import testrpcServerStub;


/**

* @author Noshoeman

*/

public class TestServer extends testrpcServerStub {

    /**

    * @param bindAddr

    * @param port

    * @throws OncRpcException

    * @throws IOException

    */

    public TestServer(InetAddress bindAddr, int port) throws OncRpcException,

        IOException {

        super(bindAddr, port);

    }


    /*

    * 这是第一个远程过程

    * @see testrpcServerStub#TEST_TEST(java.lang.String)

    */

    public mcps_string TEST_TEST(String arg1) {

        System.out.println("This is test function! " + arg1);

        return null;

    }


    /*

    * 这是第二个远程过程

    * @see testrpcServerStub#TEST_DO_PROCESS(java.lang.String)

    */

    public mcps_string TEST_DO_PROCESS(String arg1) {

        System.out.println("Got msg from client: " + arg1);

        return null;

    }


    /**

    * 服务器端的主函数

    * @param args

    */

    public static void main(String[] args) {

        try {

            System.out.println("Server starting...");

            InetAddress address = null;

            try {

                address = InetAddress.getLocalHost();

                System.out.println(address.toString());

            } catch (UnknownHostException e) {

                System.out.println("-------");

                e.printStackTrace();

            }

            TestServer server = new TestServer(address, 2023);

            System.out.println("Is server null? " + (server == null ? true : false));

            server.run();

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

}

//////////////////////////////////////////////////////////

  这样一来,服务器的测试例程就写好了。不愿意用ant来编译的“懒友”(me too),可以用eclipse建立两个相同的copy工程来分别编译并执行。先run服务器端,然后再run客户端,就可以看到当客户端run起来以后,服务器端的控制台出现字符:

Got msg from client: Hello!

恭喜,大功告成!


  6. 一些不愿意见到的事

  在这过程中,可能不会太顺利。但其实事情也并非不顺,只是多了些波澜,而往往就是这些波澜,让我们痛苦不堪……

  你可能无法正确调用jrpcgen,请注意Java相关的路径设置;如果是在1.5x下尝试始终有问题的话,请换1.4x试试。

  你可能会编译不过,请注意引入所需要的jar文件,在下载Remoteteabin文件中有。

  但我在这里其实想说的是在run的时候出错。在run服务器端的时候,控制台打出了:Is server null?false这样的信息,但是在程序坚持一小会儿以后,就会出现:

org.acplt.oncrpc.OncRpcException: ONC/RPC portmap failure

at org.acplt.oncrpc.OncRpcPortmapClient.setPort(OncRpcPortmapClient.java:314)

at org.acplt.oncrpc.server.OncRpcUdpServerTransport.register(OncRpcUdpServerTransport.java:215)

at org.acplt.oncrpc.server.OncRpcServerStub.register(OncRpcServerStub.java:100)

at org.acplt.oncrpc.server.OncRpcServerStub.run(OncRpcServerStub.java:80)

at test.TestServer.main(TestServer.java:89)

  如果是遇到的这个错误,那么想说的是恭喜你,因为这表明你的程序本身已经没有问题了;问题只在于portmap

  7. 如何解决ONC/RPC portmap failure

  要说清楚怕也是可以写一本小书了。在这里我们不打算细究这个问题,我要说的是如何解决上面遇到的问题。(当然没有遇到的话是甚好)

       portmap 即“端口映射”,是一个server , 主要功能是转换 TCP/IP 通讯协定的port变成 RPC program number , 因为这样客户端才能做RPC calls

       所以,显然,在之前Remotetea包中的一个portmap.jar,则是一个基于Java实现的,兼容Sunportmapprotocol version 2ONC/RPC portmap

  这样的话,要解决其实很简单了。如果在Linux下,一般已经在/sbin/portmap下有了,man一下用法,其实就敲入portmap便好;如果在Windows下,则启用在Remotetea包中带的portmap.jar就可以了:java -jar jportmap.jar。如果在Windows下还有问题,请注意在系统管理的“服务(service)”里,有两个关于RPC的服务,打开再试试。当然,在Linux下,也可以同样使用这里的portmap.jar

  唔,好了,就写到这里罢。

上一篇: bash shell中,单引号、 双引号,反引号(``)的区别及各种括号的区别
下一篇:搞定svn+apache的配置在linux下,同时可以用网页来浏览