RPC 远程调用 原理

     RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

 

基本简介

 

RPC采用客户机/服务器 模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息的到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后, 客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
有多种 RPC 模式和执行。最初由 Sun 公司提出。IETF ONC 宪章重新修订了 Sun 版本,使得 ONC RPC 协议成为 IETF 标准协议。现在使用最普遍的 模式和执行是开放式 软件基础的分布式计算环境(DCE)。

 

工作原理

 

运行时,一次客户机对服务器的RPC调用,其内部操作大致有如下十步:
1.调用 客户端句柄;执行传送参数
2.调用本地系统 内核发送网络 消息
3. 消息传送到远程 主机
4.服务器句柄得到 消息并取得参数
5.执行远程过程
6.执行的过程将结果返回服务器句柄
7.服务器句柄返回结果,调用远程系统 内核
8.消息传回 本地主机
9.客户句柄由 内核接收 消息
10.客户接收句柄返回的数据
RPC OVER HTTP
Microsoft RPC-over-HTTP 部署(RPC over HTTP)允许RPC 客户端安全和有效地通过Internet 连接到RPC 服务器程序并执行 远程过程调用。这是在一个名称为RPC-over-HTTP 代理,或简称为RPC 代理的 中间件的帮助下完成的。
RPC 代理运行在IIS 计算机上。它接受来自Internet 的RPC 请求,在这些请求上执行认证,检验和访问检查,如果请求通过所有的测试,RPC 代理将请求转发给执行真正处理的RPC 服务器。通过RPC over HTTP,RPC 客户端不和服务器直接通信,它们使用RPC 代理作为 中间件。

 

协议结构

 

远程过程调用(RPC)信息协议由两个不同结构组成:调用信息和答复信息。信息流程如下所示:
RPC: 远程过程调用流程
RPC 调用信息:每条 远程过程调用信息包括以下无符号整数字段,以独立识别远程过程:
程序号(Program number)
程序版本号(Program version number)
过程号(Procedure number)
RPC 调用信息主体形式如下:
struct call_body {
unsigned int rpcvers;
unsigned int prog;
unsigned int vers;
unsigned int proc;
opaque_auth cred;
opaque_auth verf;
1 parameter
2 parameter . . . };
RPC 答复信息:RPC 协议的答复信息的改变取决于 网络服务器对调用信息是接收还是拒绝。答复信息请求包括区别以下情形的各种信息:
RPC 成功执行调用信息。.
RPC 的远程实现不是协议第二版,返回 RPC 支持的最低和最高版本号。
在远程系统中,远程程序不可用。
远程程序不支持被请求的版本号。返回远程程序所支持的最低和最高版本号。
请求的过程号不存在。通常是呼叫方协议或程序差错。
RPC答复信息形式如下:
enum reply_stat stat
{MSG_ACCEPTED = 0,
MSG_DENIED = 1 };

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

一、RPC介绍

  什么是RPC?Remote Procedure Call,远程过程调用。也就是说,调用过程代码并不是在调用者本地运行,而是要实现调用者与被调用者二地之间的连接与通信。比较严格的定义是:Remote procedure call (RPC) is a protocol that allows a computer program running on one computer to cause a subroutine on another computer to be executed without the programmer explicitly coding the details for this interaction. When the software in question is written using object-oriented principles, RPC may be referred to as remote invocation or remote 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调用结束后,相应线程发送相应的信号,客户端程序便继续运行。

 

二、基于xml-rpc的应用简单实现

下载xml-rpc jar包 http://ws.apache.org/xmlrpc/download.html 当前版本:3.1.3

 

1、业务处理接口

package com.flyoung.xmlrpc;public interface ServicesHandler {    public String execute(String str);}

2、业务接口实现

复制代码
package com.flyoung.xmlrpc;public class HelloHandler implements ServicesHandler {    public String execute(String str) {                return "hello "+str+"!";    }}
复制代码

3、客户端

复制代码
package com.flyoung.xmlrpc;import java.net.MalformedURLException;import java.net.URL;import java.util.Vector;import org.apache.xmlrpc.XmlRpcException;import org.apache.xmlrpc.client.XmlRpcClient;import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;public class TestClient {    /**     * @param args     */    public static void main(String[] args) {        try {            //配置客户端            XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();            //设置服务器端地址            config.setServerURL(new URL("http://localhost:8080/Rpc/HelloHandler"));            //创建XmlRpc客户端            XmlRpcClient client = new XmlRpcClient();            //绑定以上设置            client.setConfig(config);            //创建参数列表            Vector params = new Vector();            params.addElement("flyoung");            //执行XML-RPC 请求            String result =(String) client.execute("HelloHandler.execute", params);            System.out.println("result:"+result);           } catch (MalformedURLException e) {            e.printStackTrace();            } catch (XmlRpcException e) {            e.printStackTrace();        }    }}
复制代码

4、服务器端

复制代码
package com.flyoung.xmlrpc;import java.io.IOException;import javax.servlet.ServletConfig;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.apache.xmlrpc.XmlRpcException;import org.apache.xmlrpc.server.PropertyHandlerMapping;import org.apache.xmlrpc.server.XmlRpcServerConfigImpl;import org.apache.xmlrpc.webserver.XmlRpcServletServer;import com.flyoung.xmlrpc.HelloHandler;public class XmlRpcServicesServlet extends HttpServlet {    private XmlRpcServletServer server;    @Override    public void init(ServletConfig config) throws ServletException {        super.init(config);        try {            //创建XmlRpcServletServer对象            server = new XmlRpcServletServer();                        //set up handler mapping of XmlRpcServletServer object            PropertyHandlerMapping pmp = new PropertyHandlerMapping();            pmp.addHandler("HelloHandler", HelloHandler.class);            server.setHandlerMapping(pmp);                        //more config of XmlRpcServletServer object            XmlRpcServerConfigImpl serverConfig = (XmlRpcServerConfigImpl)server.getConfig();            serverConfig.setEnabledForExtensions(true);            serverConfig.setContentLengthOptional(false);        } catch (XmlRpcException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }            @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp)            throws ServletException, IOException {                server.execute(req, resp);    }    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp)            throws ServletException, IOException {                server.execute(req, resp);    }        }
复制代码

5、xml配置

复制代码
xml version="1.0" encoding="UTF-8"?><web-app version="2.5"     xmlns="http://java.sun.com/xml/ns/javaee"     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee     http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">  <display-name>display-name>      <welcome-file-list>    <welcome-file>index.jspwelcome-file>  welcome-file-list>  <servlet>      <servlet-name>XmlRpcServerservlet-name>      <servlet-class>com.flyoung.xmlrpc.XmlRpcServicesServletservlet-class>  servlet>  <servlet-mapping>     <servlet-name>XmlRpcServerservlet-name>      <url-pattern>/HelloHandlerurl-pattern>  servlet-mapping>web-app>
复制代码

6、测试结果

result:hello flyoung!

  在RPC中,当一个请求到达RPC服务器时,这个请求就包含了一个参数集和一个文本值,通常形成“classname.methodname”的形式。这就向RPC服务器表明,被请求的方法在为“classname”的类中,名叫“methodname”。然后RPC服务器就去搜索与之相匹配的类和方法,并把它作为那种方法参数类型的输入。这里的参数类型是与RPC请求中的类型是匹配的。一旦匹配成功,这个方法就被调用了,其结果被编码后返回客户方。

  缺点:

  1)XML-RPC的消息系统过于简单,没有完整意义上的消息模型 
  2)XML-RPC调用服务的方式要求直接指定对象和方法,称不上完整的面向服务的体系 
  3)XML-RPC服务器端提供的服务实际上是特定对象的某个方法,限制了服务器端的开发 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Apache Thrift是一个facebook简历的RPC框架,现在是一个Apache的顶级项目。Thrift允许通过一个跨语言的定义文件的方式定义数据类型和服务接口,这个文件作为RPC客户端和服务器通信的标准,你也可以去看看Thrift的白皮书了解更多信息。

根据Apache Thrift的官方站点的描述,Thrift是一个:

software framework, for scalable cross-language services development, combines a software stack with a code generation engine to build services that work efficiently and seamlessly between C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi and other languages.
使用Java快速入门Thrift 
安装Thrift比较的烦,但是在Windows下官方编译了一个thrift.exe,下载安装就行了。 

写 Thrift定义文件(.thrift file)

如果你之前有接触过这个东西的话,写定义文件非常的简单。但这里可以参考官方的教程快速开始。

示例定义文件(add.thrift)

1 namespace java com.eviac.blog.samples.thrift.server  // defines the namespace  
2    
3 typedef i32 int //typedefs to get convenient names for your types 
4    
5 service AdditionService {  // defines the service to add two numbers 
6         int add(1:int n1, 2:intn2), //defines a method 
7 }

编译Thrift定义文件

下面的命令编译.thrift文件

1 thrift --gen ""

对于我的例子来讲,命令是:

1 thrift --gen java add.thrift

在执行完代码后,在gen-java目录下你会发现构建RPC服务器和客户端有用的源代码。在我的例子中我将创建一个叫做AddtionService.java的java文件。

写一个 service handler

Service handler 类必须实现 AdditionService.Iface接口。
示例Service handler(AdditionServiceHandler.java)

01 packagecom.eviac.blog.samples.thrift.server; 
02    
03 importorg.apache.thrift.TException; 
04    
05 public class AdditionServiceHandler implementsAdditionService.Iface { 
06    
07  @Override 
08  public int add(int n1, int n2) throwsTException { 
09   returnn1 + n2; 
10  
11    
12 }

写一个简单的服务器

下面的示例代码是一个简单的Thrift服务器。可以看到下面的代码中有一段是注释了的,可以去掉注释来启用多线程服务器。
示例服务器(MyServer.java)

01 packagecom.eviac.blog.samples.thrift.server; 
02    
03 importorg.apache.thrift.transport.TServerSocket; 
04 importorg.apache.thrift.transport.TServerTransport; 
05 importorg.apache.thrift.server.TServer; 
06 importorg.apache.thrift.server.TServer.Args; 
07 importorg.apache.thrift.server.TSimpleServer; 
08    
09 public classMyServer { 
10    
11  public static voidStartsimpleServer(AdditionService.Processor processor) { 
12   try
13    TServerTransport serverTransport = newTServerSocket(9090); 
14    TServer server = newTSimpleServer( 
15      newArgs(serverTransport).processor(processor)); 
16    
17    // Use this for a multithreaded server 
18    // TServer server = new TThreadPoolServer(new 
19    // TThreadPoolServer.Args(serverTransport).processor(processor)); 
20    
21    System.out.println("Starting the simple server..."); 
22    server.serve(); 
23   catch(Exception e) { 
24    e.printStackTrace(); 
25   
26  
27     
28  public static voidmain(String[] args) { 
29   StartsimpleServer(new AdditionService.Processor(newAdditionServiceHandler())); 
30  
31    
32 }

 

写一个客户端

下面的例子是一个使用Java写的客户端短使用AdditionService的服务。

 

01 packagecom.eviac.blog.samples.thrift.client; 
02    
03 importorg.apache.thrift.TException; 
04 importorg.apache.thrift.protocol.TBinaryProtocol; 
05 importorg.apache.thrift.protocol.TProtocol; 
06 importorg.apache.thrift.transport.TSocket; 
07 importorg.apache.thrift.transport.TTransport; 
08 importorg.apache.thrift.transport.TTransportException; 
09    
10 public classAdditionClient { 
11    
12  public static voidmain(String[] args) { 
13    
14   try
15    TTransport transport; 
16    
17    transport = newTSocket("localhost"9090); 
18    transport.open(); 
19    
20    TProtocol protocol = newTBinaryProtocol(transport); 
21    AdditionService.Client client = newAdditionService.Client(protocol); 
22    
23    System.out.println(client.add(100200)); 
24    
25    transport.close(); 
26   catch(TTransportException e) { 
27    e.printStackTrace(); 
28   catch(TException x) { 
29    x.printStackTrace(); 
30   
31  
32    
33 }

运行服务端代码(MyServer.java)将会看到下面的输出。

1 Starting the simple server...

然后运行客户端代码(AdditionClient.java),将会看到如下输出。

1 300

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

在看hadoop的源代码的时候,看到hadoop实现了一个自定义的RPC,于是有了自己写代码实现RPC的想法。

RPC的全名Remote Process Call,即远程过程调用。使用RPC,可以像使用本地的程序一样使用远程服务器上的程序。下面是一个简单的RPC 调用实例,从中可以看到RPC如何使用以及好处:

 

Java代码   收藏代码
  1. public class MainClient {  
  2.     public static void main(String[] args) {  
  3.         Echo echo = RPC.getProxy(Echo.class"127.0.0.1"20382);     
  4.         System.out.println(echo.echo("hello,hello"));  
  5.     }  
  6. }  
Java代码   收藏代码
  1.   

 

 

Java代码   收藏代码
  1. public interface Echo {  
  2.     public String echo(String string);  
  3. }  

 

 

 

 

使用RPC.getProxy生成接口Echo的代理实现类。然后就可以像使用本地的程序一样来调用Echo中的echo方法。

使用RPC的好处是简化了远程服务访问。提高了开发效率。在分发代码时,只需要将接口分发给客户端使用,在客户端看来只有接口,没有具体类实现。这样保证了代码的可扩展性和安全性。

 

在看了RPCClient如何使用,我们再来定义一个RPC服务器的接口,看看服务器都提供什么操作:

 

 

Java代码   收藏代码
  1. public interface Server {  
  2.     public void stop();  
  3.     public void start();  
  4.     public void register(Class interfaceDefiner,Class impl);  
  5.     public void call(Invocation invo);  
  6.     public boolean isRunning();  
  7.     public int getPort();  
  8. }  

 

 服务器提供了start和stop方法。使用register注册一个接口和对应的实现类。call方法用于执行Invocation指定的接口的方法名。isRunning返回了服务器的状态,getPort()则返回了服务器使用的端口。

 

来看看Invocation的定义:

 

 

Java代码   收藏代码
  1. public class Invocation implements Serializable{  
  2.     /** 
  3.      *  
  4.      */  
  5.     private static final long serialVersionUID = 1L;  
  6.       
  7.     private Class interfaces;  
  8.     private Method method;  
  9.     private Object[] params;  
  10.     private Object result;  
  11.       
  12.       
  13.     /** 
  14.      * @return the result 
  15.      */  
  16.     public Object getResult() {  
  17.         return result;  
  18.     }  
  19.     /** 
  20.      * @param result the result to set 
  21.      */  
  22.     public void setResult(Object result) {  
  23.         this.result = result;  
  24.     }  
  25.     /** 
  26.      * @return the interfaces 
  27.      */  
  28.     public Class getInterfaces() {  
  29.         return interfaces;  
  30.     }  
  31.     /** 
  32.      * @param interfaces the interfaces to set 
  33.      */  
  34.     public void setInterfaces(Class interfaces) {  
  35.         this.interfaces = interfaces;  
  36.     }  
  37.     /** 
  38.      * @return the method 
  39.      */  
  40.     public Method getMethod() {  
  41.         return method;  
  42.     }  
  43.     /** 
  44.      * @param method the method to set 
  45.      */  
  46.     public void setMethod(Method method) {  
  47.         this.method = method;  
  48.     }  
  49.     /** 
  50.      * @return the params 
  51.      */  
  52.     public Object[] getParams() {  
  53.         return params;  
  54.     }  
  55.     /** 
  56.      * @param params the params to set 
  57.      */  
  58.     public void setParams(Object[] params) {  
  59.         this.params = params;  
  60.     }  
  61.     @Override  
  62.     public String toString() {  
  63.         return interfaces.getName()+"."+method.getMethodName()+"("+Arrays.toString(params)+")";  
  64.     }  
  65.       
  66. }  

 

 

 

     具体服务器实现类中的call方法是这样使用Invocation的:

 

 

 

Java代码   收藏代码
  1. @Override  
  2.         public void call(Invocation invo) {  
  3.             Object obj = serviceEngine.get(invo.getInterfaces().getName()); //根据接口名,找到对应的处理类  
  4.             if(obj!=null) {  
  5.                 try {  
  6.                     Method m = obj.getClass().getMethod(invo.getMethod().getMethodName(), invo.getMethod().getParams());  
  7.                     Object result = m.invoke(obj, invo.getParams());  
  8.                     invo.setResult(result);  
  9.                 } catch (Throwable th) {  
  10.                     th.printStackTrace();  
  11.                 }  
  12.             } else {  
  13.                 throw new IllegalArgumentException("has no these class");  
  14.             }  
  15.         }  

 

 

  下面来看服务器接收连接并处理连接请求的核心代码:

 

 

 

Java代码   收藏代码
  1. public class Listener extends Thread {  
  2.     private ServerSocket socket;  
  3.     private Server server;  
  4.   
  5.     public Listener(Server server) {  
  6.         this.server = server;  
  7.     }  
  8.   
  9.     @Override  
  10.     public void run() {  
  11.   
  12.         System.out.println("启动服务器中,打开端口" + server.getPort());  
  13.         try {  
  14.             socket = new ServerSocket(server.getPort());  
  15.         } catch (IOException e1) {  
  16.             e1.printStackTrace();  
  17.             return;  
  18.         }  
  19.         while (server.isRunning()) {  
  20.             try {  
  21.                   
  22.                 Socket client = socket.accept();  
  23.                 ObjectInputStream ois = new ObjectInputStream(client.getInputStream());  
  24.                 Invocation invo = (Invocation) ois.readObject();  
  25.                 server.call(invo);  
  26.                 ObjectOutputStream oos = new ObjectOutputStream(client.getOutputStream());  
  27.                 oos.writeObject(invo);  
  28.                 oos.flush();  
  29.                 oos.close();  
  30.                 ois.close();  
  31.             } catch (Exception e) {  
  32.                 // TODO Auto-generated catch block  
  33.                 e.printStackTrace();  
  34.             }  
  35.   
  36.         }  
  37.   
  38.         try {  
  39.             if (socket != null && !socket.isClosed())  
  40.                 socket.close();  
  41.         } catch (IOException e) {  
  42.             // TODO Auto-generated catch block  
  43.             e.printStackTrace();  
  44.         }  
  45.     }  
  46. }  

 

 

RPC具体的Server类是这样来使用Listener的:

 

Java代码   收藏代码
  1. public static class RPCServer implements Server{  
  2.         private int port = 20382;  
  3.         private Listener listener;   
  4.         private boolean isRuning = true;  
  5.           
  6.         /** 
  7.          * @param isRuning the isRuning to set 
  8.          */  
  9.         public void setRuning(boolean isRuning) {  
  10.             this.isRuning = isRuning;  
  11.         }  
  12.   
  13.         /** 
  14.          * @return the port 
  15.          */  
  16.         public int getPort() {  
  17.             return port;  
  18.         }  
  19.   
  20.         /** 
  21.          * @param port the port to set 
  22.          */  
  23.         public void setPort(int port) {  
  24.             this.port = port;  
  25.         }  
  26.   
  27.         private Map serviceEngine = new HashMap();  
  28.           
  29.           
  30.         @Override  
  31.         public void call(Invocation invo) {  
  32.             System.out.println(invo.getClass().getName());  
  33.             Object obj = serviceEngine.get(invo.getInterfaces().getName());  
  34.             if(obj!=null) {  
  35.                 try {  
  36.                     Method m = obj.getClass().getMethod(invo.getMethod().getMethodName(), invo.getMethod().getParams());  
  37.                     Object result = m.invoke(obj, invo.getParams());  
  38.                     invo.setResult(result);  
  39.                 } catch (Throwable th) {  
  40.                     th.printStackTrace();  
  41.                 }  
  42.             } else {  
  43.                 throw new IllegalArgumentException("has no these class");  
  44.             }  
  45.         }  
  46.   
  47.         @Override  
  48.         public void register(Class interfaceDefiner, Class impl) {  
  49.             try {  
  50.                 this.serviceEngine.put(interfaceDefiner.getName(), impl.newInstance());  
  51.                 System.out.println(serviceEngine);  
  52.             } catch (Throwable e) {  
  53.                 // TODO Auto-generated catch block  
  54.                 e.printStackTrace();  
  55.             }   
  56.         }  
  57.   
  58.         @Override  
  59.         public void start() {  
  60.             System.out.println("启动服务器");  
  61.             listener = new Listener(this);  
  62.             this.isRuning = true;  
  63.             listener.start();  
  64.         }  
  65.   
  66.         @Override  
  67.         public void stop() {  
  68.             this.setRuning(false);  
  69.         }  
  70.   
  71.         @Override  
  72.         public boolean isRunning() {  
  73.             return isRuning;  
  74.         }  
  75.           
  76.     }  

 

    服务器端代码搞定后,来看看客户端的代码,先看看我们刚开始使用RPC.getProxy方法:

 

Java代码   收藏代码
  1. public static  T getProxy(final Class clazz,String host,int port) {  
  2.           
  3.         final Client client = new Client(host,port);  
  4.         InvocationHandler handler = new InvocationHandler() {  
  5.               
  6.             @Override  
  7.             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  8.                 Invocation invo = new Invocation();  
  9.                 invo.setInterfaces(clazz);  
  10.                 invo.setMethod(new org.jy.rpc.protocal.Method(method.getName(),method.getParameterTypes()));  
  11.                 invo.setParams(args);  
  12.                 client.invoke(invo);  
  13.                 return invo.getResult();  
  14.             }  
  15.         };  
  16.         T t = (T) Proxy.newProxyInstance(RPC.class.getClassLoader(), new Class[] {clazz}, handler);  
  17.         return t;  
  18.     }  

 

Client类的代码如下:

 

Java代码   收藏代码
  1. public class Client {  
  2.     private String host;  
  3.     private int port;  
  4.     private Socket socket;  
  5.     private ObjectOutputStream oos;  
  6.     private ObjectInputStream ois;  
  7.   
  8.     public String getHost() {  
  9.         return host;  
  10.     }  
  11.   
  12.   
  13.     public void setHost(String host) {  
  14.         this.host = host;  
  15.     }  
  16.   
  17.     public int getPort() {  
  18.         return port;  
  19.     }  
  20.     public void setPort(int port) {  
  21.         this.port = port;  
  22.     }  
  23.   
  24.     public Client(String host, int port) {  
  25.         this.host = host;  
  26.         this.port = port;  
  27.     }  
  28.   
  29.     public void init() throws UnknownHostException, IOException {  
  30.         socket = new Socket(host, port);  
  31.         oos = new ObjectOutputStream(socket.getOutputStream());  
  32.     }  
  33.   
  34.     public void invoke(Invocation invo) throws UnknownHostException, IOException, ClassNotFoundException {  
  35.         init();  
  36.         System.out.println("写入数据");  
  37.         oos.writeObject(invo);  
  38.         oos.flush();  
  39.         ois = new ObjectInputStream(socket.getInputStream());  
  40.         Invocation result = (Invocation) ois.readObject();  
  41.         invo.setResult(result.getResult());  
  42.     }  
  43.   
  44. }  

 

    至此,RPC的客户端和服务器端代码完成,启动服务器的代码如下:

 

Java代码   收藏代码
  1. public class Main {  
  2.     public static void main(String[] args) {  
  3.         Server server = new RPC.RPCServer();  
  4.         server.register(Echo.class, RemoteEcho.class);  
  5.         server.start();  
  6.     }  
  7.   
  8. }  

 

   现在先运行服务器端代码,再运行客户端代码,就可以成功运行。

   详细的代码,参考附件的源代码。

 

    在写这个RPC时,没有想太多。在数据串行化上,使用了java的标准io序列化机制,虽然不能跨平台,但是做DEMO还是不错的;另外在处理客户端请求上,使用了ServerSocket,而没有使用ServerSocketChannel这个java nio中的新特性;在动态生成接口的实现类上,使用了java.lang.reflet中的Proxy类。他可以动态创建接口的实现类。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

远程对象方法调用并不是新概念,远程过程调用 (RPC) 已经使用很多年了。远程过程调用被设计为在应用程序间通信的平台中立的方式,它不理会操作系统之间以及语言之间的差异。即 RPC 支持多种语言,而 RMI 只支持 Java 写的应用程序。 [1]

另外 RMI 调用远程对象方法,允许方法返回 Java 对象以及基本数据类型。而 RPC 不支持对象的概念,传送到 RPC 服务的消息由外部数据表示 (External Data Representation, XDR) 语言表示,这种语言抽象了字节序类和数据类型结构之间的差异。只有由 XDR 定义的数据类型才能被传递, RPC 不允许传递对象。可以说 RMI 是面向对象方式的 Java RPC 。

Java 消息服务 ( Java Messaging Service, JMS ) 是一种允许应用程序创建、发送、接受和读取消息的Java API 。 JMS 与 RMI 的区别在于,采用 JMS 服务,对象是在物理上被异步从网络的某个 JVM 上直接移动到另一个 JVM 上。而 RMI 对象是绑定在本地 JVM 中,只有函数参数和返回值是通过网络传送的。

 

CORBA 是 90 年代初有 OMG 组织提出的一个分布式互操作标准,属于语言中立的。而 RMI 直接把分布式对象模型嵌入到 Java 语言的内部,使得 Java程序员可以自然的编写分布式程序,不必离开 Java 环境,或者涉及 CORBA IDL 以及 Java 到 CORBA 的类型转换。然而 RMI 不遵守 CORBA 标准,基本上是Java-to-Java 技术,难以实现与其他语言编写的对象之间的互操作。 [1]

RMI 和 CORBA 常被视为相互竞争的技术,因为两者都提供对远程分布式对象的透明访问。但这两种技术实际上是相互补充的,一者的长处正好可以弥补另一者的短处。 RMI 和 CORBA 的结合产生了 RMI-IIOP, RMI-IIOP 是企业服务器端 Java 开发的基础。

1997 年, IBM 和 Sun Microsystems 启动了一项旨在促进 Java 作为企业开发技术的发展的合作计划。两家公司特别着力于如何将 Java 用作服务器端语言,生成可以结合进现有体系结构的企业级代码。所需要的就是一种远程传输技术,它兼有 Java 的 RMI ( Remote Method Invocation ,远程方法调用)较少的资源占用量和更成熟的 CORBA ( Common Object Request Broker Architecture ,公共对象请求代理体系结构)技术的健壮性。出于这一需要, RMI-IIOP问世了,它帮助将 Java 语言推向了目前服务器端企业开发的主流语言的领先地位 。

DCOM 是从 COM 改造过来的, COM 这一技术部分是作为规范,定义了对象实现的二进制标准,用于单机上应用之间的通信,对象实现与使用的语言无关。 DCOM 是 COM 的分布式扩展,在 DCE RPC 之上构造对象的远程过程调用层支持对远程对象的访问。一个 DCOM 对象是支持一个或多个接口的组件。DCOM 对象不支持对象 ID ,因此,客户程序不能与某个特定的对象发生联系。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

主要利用socket通信,反射,代理实现类似RMI的RPC框架

 

首先是框架的代码

 

[java]  view plain copy
 
  1. package framework;  
  2.   
  3. import java.io.ObjectInputStream;  
  4. import java.io.ObjectOutputStream;  
  5. import java.lang.reflect.InvocationHandler;  
  6. import java.lang.reflect.Method;  
  7. import java.lang.reflect.Proxy;  
  8. import java.net.ServerSocket;  
  9. import java.net.Socket;  
  10.   
  11. /** 
  12.  * RpcFramework 
  13.  *  
  14.  * @author william.liangf 
  15.  */  
  16. public class RpcFramework {  
  17.   
  18.     /** 
  19.      * 暴露服务 
  20.      *  
  21.      * @param service 服务实现 
  22.      * @param port 服务端口 
  23.      * @throws Exception 
  24.      */  
  25.     public static void export(final Object service, int port) throws Exception {  
  26.         if (service == null)  
  27.             throw new IllegalArgumentException("service instance == null");  
  28.         if (port <= 0 || port > 65535)  
  29.             throw new IllegalArgumentException("Invalid port " + port);  
  30.         System.out.println("Export service " + service.getClass().getName() + " on port " + port);  
  31.         ServerSocket server = new ServerSocket(port);  
  32.         for(;;) {  
  33.             try {  
  34.                 final Socket socket = server.accept();//服务器端一旦收到消息,就创建一个线程进行处理  
  35.                 new Thread(new Runnable() {  
  36.                     @Override  
  37.                     public void run() {  
  38.                         try {  
  39.                             try {  
  40.                                 ObjectInputStream input = new ObjectInputStream(socket.getInputStream());  
  41.                                 try {  
  42.                                     String methodName = input.readUTF();//service是服务器端提供服务的对象,但是,要通过获取到的调用方法的名称,参数类型,以及参数来选择对象的方法,并调用。获得方法的名称  
  43.                                     Class[] parameterTypes = (Class[])input.readObject();//获得参数的类型  
  44.                                     Object[] arguments = (Object[])input.readObject();//获得参数  
  45.                                     ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());  
  46.                                     try {  
  47.                                         Method method = service.getClass().getMethod(methodName, parameterTypes);//通过反射机制获得方法  
  48.                                         Object result = method.invoke(service, arguments);//通过反射机制获得类的方法,并调用这个方法  
  49.                                         output.writeObject(result);//将结果发送  
  50.                                     } catch (Throwable t) {  
  51.                                         output.writeObject(t);  
  52.                                     } finally {  
  53.                                         output.close();  
  54.                                     }  
  55.                                 } finally {  
  56.                                     input.close();  
  57.                                 }  
  58.                             } finally {  
  59.                                 socket.close();  
  60.                             }  
  61.                         } catch (Exception e) {  
  62.                             e.printStackTrace();  
  63.                         }  
  64.                     }  
  65.                 }).start();  
  66.             } catch (Exception e) {  
  67.                 e.printStackTrace();  
  68.             }  
  69.         }  
  70.     }  
  71.   
  72.     /** 
  73.      * 引用服务 
  74.      *  
  75.      * @param  接口泛型 
  76.      * @param interfaceClass 接口类型 
  77.      * @param host 服务器主机名 
  78.      * @param port 服务器端口 
  79.      * @return 远程服务 
  80.      * @throws Exception 
  81.      *///原理是通过代理,获得服务器端接口的一个“代理”的对象。对这个对象的所有操作都会调用invoke函数,在invoke函数中,是将被调用的函数名,参数列表和参数发送到服务器,并接收服务器处理的结果  
  82.     @SuppressWarnings("unchecked")  
  83.     public static  T refer(final Class interfaceClass, final String host, final int port) throws Exception {  
  84.         if (interfaceClass == null)  
  85.             throw new IllegalArgumentException("Interface class == null");  
  86.         if (! interfaceClass.isInterface())  
  87.             throw new IllegalArgumentException("The " + interfaceClass.getName() + " must be interface class!");  
  88.         if (host == null || host.length() == 0)  
  89.             throw new IllegalArgumentException("Host == null!");  
  90.         if (port <= 0 || port > 65535)  
  91.             throw new IllegalArgumentException("Invalid port " + port);  
  92.         System.out.println("Get remote service " + interfaceClass.getName() + " from server " + host + ":" + port);  
  93.         return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[] {interfaceClass}, new InvocationHandler() {  
  94.             public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {  
  95.                 Socket socket = new Socket(host, port);  
  96.                 try {  
  97.                     ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());  
  98.                     try {  
  99.                         output.writeUTF(method.getName());  
  100.                         output.writeObject(method.getParameterTypes());  
  101.                         output.writeObject(arguments);  
  102.                         ObjectInputStream input = new ObjectInputStream(socket.getInputStream());  
  103.                         try {  
  104.                             Object result = input.readObject();  
  105.                             if (result instanceof Throwable) {  
  106.                                 throw (Throwable) result;  
  107.                             }  
  108.                             return result;  
  109.                         } finally {  
  110.                             input.close();  
  111.                         }  
  112.                     } finally {  
  113.                         output.close();  
  114.                     }  
  115.                 } finally {  
  116.                     socket.close();  
  117.                 }  
  118.             }  
  119.         });  
  120.     }  
  121.   
  122. }  


服务接口

 

 

[java]  view plain copy
 
  1. package user;  
  2.   
  3. public interface  HelloService {  
  4.     String hello(String name);  
  5. }  


实现服务

 

 

[java]  view plain copy
 
  1. package user;  
  2.   
  3. public class HelloServiceImpl implements HelloService{  
  4.     public String hello(String name) {  
  5.         return "Hello " + name;  
  6.     }  
  7.   
  8. }  


服务器

 

 

[java]  view plain copy
 
  1. package user;  
  2. import framework.RpcFramework;  
  3.   
  4. public class Server {  
  5.     public static void main(String []args) throws Exception {  
  6.         HelloService service = new HelloServiceImpl();  
  7.         RpcFramework.export(service, 1234);   
  8.     }  
  9. }  


客户机

 

 

[java]  view plain copy
 
  1. package user;  
  2. import framework.RpcFramework;  
  3.   
  4. public class Client {  
  5.     public static void main(String[] args) throws Exception {    
  6.         HelloService service = RpcFramework.refer(HelloService.class"127.0.0.1"1234);    
  7.         for (int i = 0; i < Integer.MAX_VALUE; i ++) {    
  8.             String hello = service.hello("World" + i);    
  9.             System.out.println(hello);    
  10.             Thread.sleep(1000);    
  11.         }    
  12.     }    
  13. }  



 

 

 

javac framework/RpcFramework.java 

javac -classpath . user/*.java

 

java -classpath . user.Server

 

java -classpath . user.Client

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Java RPC通信机制之XML-RPC:Apache XML-RPC 3.0开发简介

摘要:

XML-RPC是一种简单的,轻量级的通过HTTP协议进行RPC通信的规范。本文以Apache XML-RPC 3.0为基础,对XML-RPC的基本原理及Apache XML-RPC 3.0的主要特性进行了讨论和分析。

正文:

一、概述

XML-RPC是一种简单的,轻量级的通过HTTP协议进行RPC通信的规范。一个XML-RPC消息就是一个请求体为XML的HTTP-POST请求,被调用的方法在服务器端执行并将执行结果以XML格式编码后返回。

以下是通过ethereal抓到的一个典型的XML-RPC调用包(为便于阅读,进行了格式化):

POST /xmlrpc HTTP/1.1

Content-Type: text/xml

User-Agent: Apache XML RPC 3.0 (Jakarta Commons httpclient Transport)

Host: 135.252.156.147:8080

Content-Length: 260

xml version="1.0" encoding="UTF-8"?>

<methodCall xmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">

      <methodName>Calculator.addmethodName>

      <params>

            <param>

                  <value>

                        <i4>2i4>

                  value>

            param>

            <param>

                  <value>

                        <i4>3i4>

                  value>

            param>

      params>

methodCall>

而对应的返回数据包为:

HTTP/1.1 200 OK

Server: Apache XML-RPC 1.0

Connection: close

Content-Type: text/xml

Content-Length: 189

xml version="1.0" encoding="UTF-8"?>

<methodResponse xmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">

      <params>

            <param>

                  <value>

                        <i4>5i4>

                  value>

            param>

      params>

methodResponse>

其格式很简单,几乎是不言自明的,分别用methodCall和methodResponse标签标识发送给Server的调用请求和Server的返回结果,请求方法的名称用methodName标识,参数用params和param标识,而参数的类型标签则如下表所示:

Tag

Java Type

说明

or

Integer/int

4字节带符号整数值

Boolean

0 (false) or 1 (true)

String

字符串

Double

双精度带符号浮点值

java.util.Date

日期/时间

byte[]

base64编码的二进制数据

java.util.Map

键值对,键为String类型,而值为任意有效类型

Object[]

java.util.List

对象数组

二、举例

下面举一个实际运用XML-RPC进行RPC调用的例子,XML-RPC规范有多种针对不同语言的实现,这里我们使用的是Apache的XML-RPC3.0RC1。

在开始之前,需到http://jakarta.apache.org/commons/index.html下载如下程序包:

commons-codec-1.3(通用编码/解码算法实现,可参考http://www.devx.com/Java/Article/29795/1954?pf=true或http://jakarta.apache.org/commons/codec/userguide.html来获得该软件包的详细信息)

commons-httpclient-3.0.1(HTTP协议的客户端编程工具包,详细介绍见http://www-128.ibm.com/developerworks/cn/opensource/os-httpclient/)

将上述通用工具包解压后,拷贝其中的jar文件到XML-RPC解压目录的dist目录中。

并添加如下环境变量:

XMLRPC_HOME      XML-RPC的解压目录

XMLRPC_LIB        %XMLRPC_HOME%/dist

XMLRPCCLASSPATH      %XMLRPC_LIB%/xmlrpc-common-3.0rc1.jar;%XMLRPC_LIB%/xmlrpc-server-3.0rc1.jar;%XMLRPC_LIB%/xmlrpc-client-3.0rc1.jar;%XMLRPC_LIB%/commons-httpclient-3.0.1.jar;%XMLRPC_LIB%/commons-codec-1.3.jar

 

整个应用很简单,通过XML-RPC调用Server端提供的HelloHandler.sayHello方法回显一个字符串信息。下面是HelloHandler接口及其实现类相关代码:

// HelloHandler.java

package demo.xmlrpc;

 

public interface HelloHandler {

      public String sayHello(String str);

}

 

// HelloHandlerImpl.java

package demo.xmlrpc;

 

public class HelloHandlerImpl implements HelloHandler {

      public String sayHello(String str){

            return "Hello, " + str + "!";

      }

}

以下是对应的Server端源代码:

// Server1.java

package demo.xmlrpc;

 

import java.io.IOException;

import java.io.OutputStream;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

 

import org.apache.xmlrpc.XmlRpcException;

import org.apache.xmlrpc.server.PropertyHandlerMapping;

import org.apache.xmlrpc.server.XmlRpcServerConfigImpl;

import org.apache.xmlrpc.webserver.XmlRpcServletServer;

 

public class Server1 extends HttpServlet {

      private XmlRpcServletServer server;

     

      public void init(ServletConfig pConfig) throws ServletException {

            super.init(pConfig);

            try {

                  // create a new XmlRpcServletServer object

                  server = new XmlRpcServletServer();

                  // set up handler mapping of XmlRpcServletServer object

                  PropertyHandlerMapping phm = new PropertyHandlerMapping();

                  phm.addHandler("HelloHandler", HelloHandlerImpl.class);                

                  server.setHandlerMapping(phm);

                  // more config of XmlRpcServletServer object     

                  XmlRpcServerConfigImpl serverConfig = (XmlRpcServerConfigImpl)server.getConfig();

                  serverConfig.setEnabledForExtensions(true);

                  serverConfig.setContentLengthOptional(false);

            } catch (XmlRpcException e) {

                  try {

                        log("Failed to create XmlRpcServer: " + e.getMessage(), e);

                  } catch (Throwable ignore) {

                  }

                  throw new ServletException(e);

            }

      }

     

      public void doPost(HttpServletRequest pRequest, HttpServletResponse pResponse)

            throws IOException, ServletException {

            server.execute(pRequest, pResponse);

      }

}

以下是对应的Client端源代码:

// Client1.java

package demo.xmlrpc;

 

import java.io.IOException;

import java.net.MalformedURLException;

import java.util.Vector;

import java.net.URL;

 

import org.apache.xmlrpc.XmlRpcException;

import org.apache.xmlrpc.client.XmlRpcClient;

import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;

 

public class Client1 {

      public static void main(String[] args) {

            try {

                  // config client

                  XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();

                  config.setServerURL(newURL("http://localhost:8080/jsp/XmlRpcServer"));      // should be modified according to your configuration of jsp container

                  // create a new XmlRpcClient object and bind above config object with it

                  XmlRpcClient client = new XmlRpcClient();

                  client.setConfig(config);

                  // create parameter list

                  Vector params = new Vector();

                  params.addElement("Tom");

                  // execute XML-RPC call

                  String result = (String) client.execute("HelloHandler.sayHello", params);                 

                  System.out.println(result);

            } catch (MalformedURLException e) {

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

            } catch (XmlRpcException e) {

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

            } catch (IOException e) {

                  e.printStackTrace();

            }

      }

}

程序源码中已包含了详细的注释,这里就不作过多解释了。但需注意XmlRpcDemo_Client中的ServerURL信息应根据自己的的jsp容器的配置作相应调整,并需设置相应的servlet-mapping信息,在我的jsp目录(Tomcat5.5的Context之一)下的WEB_INF/web.xml文件中存在如下的servlet-mapping信息:

<servlet>

      <servlet-name>XmlRpcServerservlet-name>

      <servlet-class>demo.xmlrpc.Server1servlet-class>

servlet>

<servlet-mapping>

      <servlet-name>XmlRpcServerservlet-name>

      <url-pattern>/XmlRpcServerurl-pattern>

servlet-mapping>

并且,上述Server1.class及其他相关类文件已被拷贝到jsp/WEB-INF/classes/demo/xmlrpc目录下。

在启动Tomcat并执行

java -classpath %CLASSPATH%;%XMLRPCCLASSPATH% demo.xmlrpc.Client1.java

前,你应该将%XMLRPC_HOME%/dist、%XMLRPC_HOME%/lib下的几个jar文件(source就不用拷了)及前面下载的commons-codec-1.3.jar拷贝到%TOMCAT_HOME%/common/lib或jsp/WEB-INF/lib下。

Note:除了上面这种方式,你可以无需编写任何Server端代码,仅通过简单配置完成上述功能,具体可参考:http://ws.apache.org/xmlrpc/server.html

接下来,作为比较,我们来看看XML-RPC2.0中应该如何实现上述功能。

以下是2.0版的Server程序:

// Server2.java

package demo.xmlrpc;

 

import java.io.IOException;

import java.io.OutputStream;

 

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

 

import org.apache.xmlrpc.XmlRpcServer;

 

public class Server2 extends HttpServlet {

      public void doPost(HttpServletRequest request, HttpServletResponse response)

                  throws ServletException, IOException {

            XmlRpcServer xmlrpc = new XmlRpcServer();

            xmlrpc.addHandler("HelloHandler"new HelloHandlerImpl());

            byte[] result = xmlrpc.execute(request.getInputStream());

            response.setContentType("text/xml");

            response.setContentLength(result.length);

            OutputStream out = response.getOutputStream();

            out.write(result);

            out.flush();

      }

}

以下是2.0版的Client程序:

// Client2.java

package demo.xmlrpc;

 

import java.io.IOException;

import java.net.MalformedURLException;

import java.util.Vector;

 

import org.apache.xmlrpc.XmlRpcClient;

import org.apache.xmlrpc.XmlRpcException;

 

public class Client2 {

      public static void main(String[] args) {

            try {

                  XmlRpcClient xmlrpc = newXmlRpcClient("http://localhost:8080/jsp/XmlRpcServer");

                  Vector params = new Vector();

                  params.addElement("Tom");

                  String result = (String) xmlrpc.execute("HelloHandler.sayHello", params);

                  System.out.println(result);

            } catch (MalformedURLException e) {

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

            } catch (XmlRpcException e) {

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

            } catch (IOException e) {

                  e.printStackTrace();

           }

      }

}

总体上看,3.0比2.0在可配置性方面有了一些改进,其它方面则没有太大变化,但由于功能模块的分离,使得3.0较2.0显得更为复杂,已经习惯了2.0单一模块风格的开发者可能需要一些时间适应这种变化。

三、其它特性

除了上面的基本功能,XML-RPC3还支持动态代理/工厂和异步通信等特性。

通过运用动态代理特性,我们可以在Server端及Client端共享接口信息,从而在编译期间进行必要的类型检查,在XML-RPC内部,所有的调用仍然是被动态转发给XmlRpcClient对象来完成的。但要使用XML-RPC3的动态代理功能,相应的服务器端的处理器类名称必须是Client端接口类的全名(含包名,该名称一般应该与Server端接口类全名一致),否则将会导致调用失败。以上面的HelloHandler接口为例,其对应的处理器类名称应该为:demo.xmlrpc.HelloHandler。

Note: 动态代理(JDK1.3引入)是Proxy模式、依赖注入(Dependency Injection)及动态代码生成等技术相结合的一种应用,在各新型Web应用框架及容器中被广泛采用。

而要使用XML-RPC的异步通信功能,只需实现org.apache.xmlrpc.client.AsyncCallback接口,该接口包括两个方法:

public void handleResult(XmlRpcRequest pRequest, Object pResult);

public void handleError(XmlRpcRequest pRequest, Throwable pError);

此外,为了便于在普通应用中使用XML-RPC,XML-RPC还提供了一个WebServer类,以便在应用中内嵌一个HTTP服务器,为Client程序提供HTTP服务。

下面的范例演示了上面提到的几种特性,以下是Server端代码:

// Server3.java

package demo.xmlrpc;

 

import org.apache.xmlrpc.server.PropertyHandlerMapping;

import org.apache.xmlrpc.server.XmlRpcServer;

import org.apache.xmlrpc.server.XmlRpcServerConfigImpl;

import org.apache.xmlrpc.webserver.WebServer;

 

public class Server3 {

      private static final int port = 8080;

 

      public static void main(String [] args) throws Exception {

            WebServer webServer = new WebServer(port);

 

            XmlRpcServer xmlRpcServer = webServer.getXmlRpcServer();

 

            PropertyHandlerMapping phm = new PropertyHandlerMapping();

            phm.addHandler("demo.xmlrpc.HelloHandler", HelloHandlerImpl.class);

             

            xmlRpcServer.setHandlerMapping(phm);

 

            XmlRpcServerConfigImpl serverConfig = (XmlRpcServerConfigImpl)xmlRpcServer.getConfig();

            serverConfig.setEnabledForExtensions(true);

            serverConfig.setContentLengthOptional(false);

 

            webServer.start();

      }

}

下面是Client端代码:

// Client3.java

package demo.xmlrpc;

 

import java.net.URL;

import java.util.List;

import java.util.Vector;

 

import org.apache.xmlrpc.XmlRpcRequest;

import org.apache.xmlrpc.XmlRpcException;

import org.apache.xmlrpc.client.XmlRpcClient;

import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;

import org.apache.xmlrpc.client.AsyncCallback;

import org.apache.xmlrpc.client.util.ClientFactory;

 

class EchoCallback implements AsyncCallback {

      public void handleResult(XmlRpcRequest pRequest, Object pResult) {

            System.out.println("Server returns: " + (String)pResult);

      }

     

      public void handleError(XmlRpcRequest pRequest, Throwable pError) {

            System.out.println("Error occurs: " + pError.getMessage());

      }

}

 

public class Client3 {

      public static void main(String [] args) throws Exception {

            // create configuration

            XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();

            config.setServerURL(new URL("http://localhost:8080/xmlrpc"));

            config.setEnabledForExtensions(true);

            config.setConnectionTimeout(60 * 1000);

            config.setReplyTimeout(60 * 1000);

 

            XmlRpcClient client = new XmlRpcClient();

            // set configuration

            client.setConfig(config);

           

            // make a call using dynamic proxy

            ClientFactory factory = new ClientFactory(client);

            HelloHandler handler = (HelloHandler)factory.newInstance(HelloHandler.class);

            String str = handler.sayHello("Bill David");

            System.out.println(str);

           

            // make an asynchronous call

            List params = new Vector(); // for JDK before 1.5, use 'List params = new Vector();'

            params.add("Tom");

            client.executeAsync("demo.xmlrpc.HelloHandler.sayHello", params, newEchoCallback());

      }

}

Note:由于Server3使用了8080端口,注意不要在Tomcat运行时启动Server3(除非你的Tomcat运行在其他端口)。

参考:

1.    XML-RPC,http://ws.apache.org/xmlrpc/

2.    XML-RPC协议,http://hedong.3322.org/archives/000470.html

3.    Dynamic Proxy Classes,http://java.sun.com/j2se/1.3/docs/guide/reflection/proxy.html

4.    透明,动态代理的前世今生,《程序员》2005年第1期。

 

 

 

 

 

 

 

 

 

 

 

 

 

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

一、RPC的介绍

  什么是RPC?Remote Procedure Call,远程过程调用。也就是说,调用过程代码并不是在调用者本地运行,而是要实现调用者与被调用者二地之间的连接与通信。比较严格的定义是:Remote procedure call (RPC) is aprotocol that allows a computer program running on one computer to cause a subroutine on another computer to be executed without the programmer explicitly coding the details for this interaction. When the software in question is written using object-oriented 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而开发纯Java的RPC应用的工具。它的特点是:

  1. 100%的纯Java开发

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

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

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

  5. 基于Java的portmapper。

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

下载Remotetea,请在http://sourceforge.net/project/showfiles.php?group_id=87517 这里察看。下载bin包并解压,可以在classes文件夹中找到jrpcgen.jar、oncrpc.jar和portmap.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.java、testrpc.java、testrpcServerStub.java、test_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文件,在下载Remotetea的bin文件中有。

  但我在这里其实想说的是在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实现的,兼容Sun的portmap、protocol version 2的ONC/RPC portmap。

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

 

 

 

 

 

 

 

 

 

 

 

 

大卫注1

写完CORBA系列后,本想接着写写其它几种典型的远程通信协议:RMIXML-RPCSOAP,但由于工作的原因,加之房子装修等麻烦事,一直没有心情动笔。今天接到装修公司老板电话说开工证要晚几天办下来,要停工4-5天,狂怒后突然有了静下心来完成原本想写的东西的想法,既来之,则安之(i.e.郁闷啊,郁闷啊,就习惯了...

大卫注2

这个系列基本上是一份笔记,没有加入太多我自己的东西,仅仅记录了自己在使用过程中遇到的问题,及其解决办法。

 

在传统的RPC编程接口逐渐淡出人们视线的同时,新的、更便于使用且附加了更多特性的RPC编程接口也不断涌现,CORBA作为分布式对象计算技术的典范,在很长一段时间内极大地吸引了大家的注意,但是由于CORBA规范试图覆盖过多的内容,使得CORBA显得过于复杂,也极大地限制了CORBA的应用范围,本系列将向大家介绍几种轻量级的,更适于在Java开发中使用的RPC编程接口:RMI、XML-RPC、SOAP。

RMI(Remote Method Invocation)

与本系列将介绍的其它两种RPC编程接口不同,RMI(Remote Method Invocation)显得有些老旧,它是在Java-IDL加入J2SE之前被引入的。RMI开发流程与CORBA如出一辙(从出现的时间上无法确定RMI是否是按照CORBA规范定制的),因此,其开发过程相对比较烦琐,但是由于RMI是EJB的基础,因此,它在Java开发中具有十分重要的地位。

以下是创建远程方法调用的5个步骤:

1.   定义一个扩展了Remote接口的接口,该接口中的每一个方法必须声明它将产生一个RemoteException异常;

2.   定义一个实现该接口的类;

3.   使用rmic程序生成远程实现所需的存根和框架;

4.   创建一个客户程序和服务器进行RMI调用;

5.   启动rmiregistry并运行自己的服务程序和客户程序。

下面举一个简单、而且被无数次引用的例子:Echo。

1、定义Echo接口

//Echo.java

//The Echo remote interface

package demo.rmi;

 

import java.rmi.*;

 

public interface Echo extends Remote {

      String echo(String msg) throws RemoteException;

}

2、实现Echo接口

//EchoServer.java

//The implementation of the Echo remote object

package demo.rmi;

 

import java.net.*;

import java.rmi.*;

import java.rmi.registry.*;

import java.rmi.server.*;

 

public class EchoServer

      extends UnicastRemoteObject

      implements Echo {

       //默认构件器,也要“掷”出RemoteException违例

      public EchoServer() throws RemoteException {

            super();

      }

 

      public String echo(String msg) throws RemoteException {

            return "Echo: " + msg;

      }

 

      public static void main(String [] args) {

            /*创建和安装一个安全管理器,令其支持RMI作为Java开发包的一部分,适用于RMI唯一一个是RMISecurityManager.*/

            System.setSecurityManager(new RMISecurityManager());

 

            try {

                     /*创建远程对象的一个或多个实例,下面是EchoServer对象*/

                  EchoServer es = new EchoServer();

                  /*向RMI远程对象注册表注册至少一个远程对象。一个远程对象拥有的方法即可生成指向其他远程对象的句柄,这样,客户到注册表里访问一次,得到第一个远程对象即可.*/

                  Naming.rebind("EchoServer", es);

                  System.out.println("Ready to provide echo service...");

            } catch (Exception e) {

                  e.printStackTrace();

            }

      }

}

这个实现类使用了UnicastRemoteObject去连接RMI系统。在我们的例子中,我们是直接的从UnicastRemoteObject这个类上继承的,事实上并不一定要这样做,如果一个类不是从UnicastRmeoteObject上继承,那必须使用它的exportObject()方法去连接到RMI。(否则,运行时将被告知无法序列化。)

如果一个类继承自UnicastRemoteObject,那么它必须提供一个构造函数并且声明抛出一个RemoteException对象(否则,会遇到编译错误)。当这个构造函数调用了super(),它就激活UnicastRemoteObject中的代码完成RMI的连接和远程对象的初始化。

3、运行rmic编译实现类,产生_Stub类

在demo.rmi.EchoServer.java上级目录下运行如下命令:

rmic demo.rmi.EchoServer

4、编写客户程序

//EchoClient.java

//Uses remote object EchoServer

package demo.rmi;

 

import java.rmi.*;

import java.rmi.registry.*;

 

public class EchoClient {

      public static void main(String [] args) {

            System.setSecurityManager(new RMISecurityManager());

 

            try {

                  Echo t = (Echo)Naming.lookup("EchoServer");

 

                  for (int i = 0; i < 10; i++) {

                        System.out.println(t.echo(String.valueOf(i)));

                  }

            } catch (Exception e) {

                  e.printStackTrace();

            }

      }

}

5、运行

编码的工作就只有这些,现在可以依次启动rmiregistry(启动rmiregistry时可以附加一个端口,一般使用默认的端口1099即可,这是默认的Naming Service运行端口)、EchoServer、EchoClient了。但是,虽然有些RMI的资料没有提到,但你运行时不可避免会遇到如下两个错误:

1)java.security.AccessControlException: access denied (java.net.SocketPermission 127.0.0.1:1099 connect,resolve)

原因很简单,RMI Server/Client程序试图通过Socket连接访问本机的rmiregistry服务(即RMI的Naming Service,其运行的默认端口是1099)。要解决这个问题,可以在运行Server/Client时指定一个Policy文件(关于Policy的更多信息,见参考2),如下:

java -Djava.security.policy=demo/rmi/policyfile.txt demo.rmi.EchoServer

Policy文件的内容为:

grant{

      permission java.net.SocketPermission "localhost:1099""connect, resolve";

};

即允许访问本机的1099端口。

或者干脆来个彻底开放:

grant {

      permission java.security.AllPermission """";

};

2)java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:

        java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:

        java.lang.ClassNotFoundException: demo.rmi.EchoServer_Stub

        ...

如果你凑巧用启动rmiregistry的终端窗口启动了EchoServer,那么你很走运,你看不到上面的错误,但如果你不是在看完这篇文章后就再也用不到RMI,那么,这个错误在那里等着你,:)。

错误很明显,rmiregistry找不到与EchoServer放在同一目录下的EchoServer_Stub,因为package所在demo.rmi目录的上级目录不在rmiregistry的classpath中,这个问题有两种解决方案:

a)在启动rmiregistry前先调整一下CLASSPATH环境变量,以目录E:/为例,执行:

set CLASSPATH=%CLASSPATH%;E:/

b)修改code,在EchoServer中通过如下代码:

Registry r = LocateRegistry.createRegistry(8111);

r.rebind("EchoServer", es);

在程序内部创建一个LocateRegistry,并将自身注册到该LocateRegistry,其中的数值8111表示LocateRegistry运行的端口。

同样,对于客户程序,也需要作相应的调整:

Registry r = LocateRegistry.getRegistry("localhost", 8111);

Echo e = (Echo)r.lookup("EchoServer");

而不是像上面例子中一样访问Naming类的static方法来访问默认的rmiregistry服务。

 

 

 

 

 

 

 

JSON-RPC协议是一种远程过程调用协议,其设计的目的就是为了简化处理。常规的处理机制是在两个对等

通信实体之间建立点对点的数据连接,在连接的生命周期内一方可能调用另一方提供的方法,为了调用一个远程

方法必须发送一个请求,而服务器端必须回答一个与该请求相对的响应除非这个请求是一个通知。

1.1 request (method invocation)

 客户端通过向远程服务发送一个请求的方式来实现对远程方法的调用,而这个请求是一个由JSON协议串行

化的对象,含有下面三个属性:

          ·method:被调用方法名;

          ·params:被调用方法的参数列表;

          ·id           :请求id,可以是任何类型,用于与响应匹配。

         1.2 response

        当方法调用完成后,服务器必须给出一个响应,而这个响应也是一个有JSON协议串行化的对象,其含有三个属性:

        ·result:是一个由被调用方法返回的对象,如果错误调用方法时,则其值为null;

        ·error  :如果没有错误调用方法,则其值为null;

         ·id        :与要响应的请求的id相同。

          1.3 notification

          一个notification请求是一个没有响应的特殊的请求,其也是由JSON协议串行化的对象,含有和一般请求相同的属性,但是其id应为null。

           2.1JSON-RPC over Stream Connection


原文地址 :http://yanguz123.iteye.com/blog/2013670

你可能感兴趣的:(RPC 远程调用 原理)