RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。 RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
运行时,一次客户机对服务器的RPC调用,其内部操作大致有如下十步:
1.调用客户端句柄;执行传送参数
2.调用本地系统内核发送网络消息
3.消息传送到远程主机
4.服务器句柄得到消息并取得参数
5.执行远程过程
6.执行的过程将结果返回服务器句柄
7.服务器句柄返回结果,调用远程系统内核
8.消息传回本地主机
9.客户句柄由内核接收消息
10.客户接收句柄返回的数据
远程对象方法调用并不是新概念,远程过程调用 (RPC)已经使用很多年了。远程过程调用被设计为在应用程序间通信的平台中立的方式,它不理会操作系统之间以及语言之间的差异。即RPC 支持多种语言,而 RMI 只支持 Java 写的应用程序.
另外 RMI( Remote MethodInvocation ,远程方法调用)调用远程对象方法,允许方法返回 Java 对象以及基本数据类型。而 RPC 不支持对象的概念,传送到 RPC 服务的消息由外部数据表示 (External Data Representation, XDR) 语言表示,这种语言抽象了字节序类和数据类型结构之间的差异。只有由 XDR 定义的数据类型才能被传递,RPC 不允许传递对象。可以说 RMI 是面向对象方式的Java RPC 。
Java消息服务 ( Java Messaging Service, JMS ) 是一种允许应用程序创建、发送、接受和读取消息的JavaAPI 。 JMS 与 RMI 的区别在于,采用 JMS 服务,对象是在物理上被异步从网络的某个 JVM 上直接移动到另一个 JVM 上。而 RMI 对象是绑定在本地 JVM 中,只有函数参数和返回值是通过网络传送的。
RPC调用协议图:
首先是建立RPC服务,约定底层的RPC传输通道(UDP或是TCP)。客户端的调用参数根据传输前所提供的目的地址及RPC上层应用程序号,通过底层的RPC传输通道转至相应的服务器,即RPCApplication Porgramme Server。客户端随即处于等待状态,以服务器等待应答或TimeOut超时信号。当服务器端获得了请求消息,会根据注册RPC时告诉RPC系统的程序入口地址执行相应的操作,并将结果返回至客户端。当一次RPC调用结束后,相应线程发送相应的信号,客户端程序便继续运行。
Remotetea是一个基于GNULGPL的开源的项目,它完全在Java 2/1.1平台上实现了ONC/RPC协议;由于是纯的100%的Java编写,所以不需要任何本地的库(nativebinary/libraries)。
简单的讲,它就是今天我们用于代替rpcgen而开发纯Java的RPC应用的工具。它的特点是:
其实在开发客户端和服务器端的程序时,RPC提供了不同层次的开发例程调用接口。不同层次的接口提供了对RPC不同程度级别的控制。一般可分为五个等级的编程接口:简单层例程、高层例程、中间层例程、专家层例程、底层例程。其中,简单层是为快速开发RPC应用服务而设计的,面向普通RPC应用;关于其他层例程,在这里就暂不提及了。简单层其函数列表如下:
Rpc_reg( )——在某一特定类型的传输层上注册一个过程,以作为提供服务的RPC程序。
Rpc_call( )——可以远程调用特定主机上的特定过程。
Rpc_Broadcast( ) ——向指定类型的所有传输端口上广播一个远程过程调用请求。
实现简单层时,便会用到Remotetea。它可以将以类C语言语法的RPC语言进行源代码编译。
所用到的文件后缀名为.x,可以称为x-文件。下面即是测试用的一个test.x文件的代码:
constMAXNAMELEN = 2048; /* maximum length of a test string */
typedefstring test_string<MAXNAMELEN>; /* a directory entry */
programTEST_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。
生成文件说明
通过用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请求。
客户端程序
publicclass TestClient extends testrpcClient {
public TestClient(InetAddress host, intport, int protocol) throws OncRpcException, IOException {
super(host, port, protocol);
}
public static void main(String[] args) {
InetAddress address = null;
try {
address =InetAddress.getLocalHost();
} catch (UnknownHostException e) {
}
try {
TestClient client = newTestClient(address,2023,OncRpcProtocols.ONCRPC_TCP);
client.TEST_DO_PROCESS("Hello!");
client.close();
} catch (OncRpcException e) {
} catch (IOException e) {
}
}
}
服务器端程序
publicclass TestServer extends testrpcServerStub {
public TestServer(InetAddress bindAddr, intport) throws OncRpcException,IOException {
super(bindAddr, port);
}
public mcps_string TEST_TEST(String arg1) {
return null;
}
public mcps_string TEST_DO_PROCESS(Stringarg1) {
return null;
}
public static void main(String[] args) {
try {
InetAddress address = null;
try {
address =InetAddress.getLocalHost();
} catch (UnknownHostException e) {
}
TestServer server = newTestServer(address, 2023);
server.run();
} catch (Exception e) {
}
}
}
JSON-RPC-Java是一个用Java来实现动态JSON-RPC的框架. 利用它内置的一个轻级量JSON-RPC JavaScripIt客户端,可以让你透明地在JavaScript中调用Java代码。JSON-RPC-Java可运行在Servlet容器中如 Tomcat也可以运行在JBoss与其它J2EE应用服务器中因此可以在一个基于JavaScript与DHTML的Web应用程序中利用它来直接调用普通Java方法与EJB方法。
基于xml-rpc的应用简单实现
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
<?xmlversion="1.0" encoding="UTF-8"?>
<methodCallxmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">
<methodName>Calculator.add</methodName>
<params>
<param>
<value>
<i4>2</i4>
</value>
</param>
<param>
<value>
<i4>3</i4>
</value>
</param>
</params>
</methodCall>
而对应的返回数据包为:
HTTP/1.1200 OK
Server:Apache XML-RPC 1.0
Connection:close
Content-Type:text/xml
Content-Length:189
<?xmlversion="1.0" encoding="UTF-8"?>
<methodResponsexmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">
<params>
<param>
<value>
<i4>5</i4>
</value>
</param>
</params>
</methodResponse>
其格式很简单,几乎是不言自明的,分别用methodCall和methodResponse标签标识发送给Server的调用请求和Server的返回结果,请求方法的名称用methodName标识,参数用params和param标识
publicinterface ServicesHandler {
public String execute(String str);
}
publicclass HelloHandler implements ServicesHandler {
public String execute(String str) {
return "hello"+str+"!";
}
}
客户端
importorg.apache.xmlrpc.*;
publicclass TestClient {
public static void main(String[] args) {
try {
//配置客户端
XmlRpcClientConfigImpl config = newXmlRpcClientConfigImpl();
//设置服务器端地址
config.setServerURL(newURL("http://localhost:8080/Rpc/HelloHandler"));
//创建XmlRpc客户端
XmlRpcClient client = newXmlRpcClient();
//绑定以上设置
client.setConfig(config);
//创建参数列表
Vector<String> params = newVector<String>();
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();
}
}
}
服务器端
publicclass XmlRpcServicesServlet extends HttpServlet {
private XmlRpcServletServer server;
public void init(ServletConfig config)throws ServletException {
super.init(config);
try {
//创建XmlRpcServletServer对象
server = new XmlRpcServletServer();
//set up handler mapping ofXmlRpcServletServer object
PropertyHandlerMapping pmp = newPropertyHandlerMapping();
pmp.addHandler("HelloHandler", HelloHandler.class);
server.setHandlerMapping(pmp);
//more config ofXmlRpcServletServer object
XmlRpcServerConfigImpl serverConfig= (XmlRpcServerConfigImpl)server.getConfig();
serverConfig.setEnabledForExtensions(true);
serverConfig.setContentLengthOptional(false);
} catch (XmlRpcException e) {
}
}
protected void doGet(HttpServletRequestreq, HttpServletResponse resp)
throws ServletException,IOException {
server.execute(req, resp);
}
protected void doPost(HttpServletRequestreq, HttpServletResponse resp)
throws ServletException,IOException {
server.execute(req, resp);
}
}
xml配置
<servlet>
<servlet-name>XmlRpcServer</servlet-name>
<servlet-class>com.flyoung.xmlrpc.XmlRpcServicesServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>XmlRpcServer</servlet-name>
<url-pattern>/HelloHandler</url-pattern>
</servlet-mapping>
JavaRMI 指的是远程方法调用 (Remote Method Invocation)。它是一种机制,能够让在某个 Java 虚拟机上的对象调用另一个 Java虚拟机中的对象上的方法。可以用此方法调用的任何对象必须实现该远程接口。
RMI的具体应用主要来所可以归纳为三个角色:
服务提供者:实现了服务接口,为其他客户端提供服务;
注册服务器:是一个提供服务注册的实体,服务器提供者需要把他的服务注册到这个才可以被其他客户查找使用;
客户端 :服务的使用者,从注册服务器查找到服务,再使用服务。
一个正常工作的RMI系统由下面几个部分组成:
远程服务的接口定义
远程服务接口的具体实现
桩(Stub)和框架(Skeleton)文件
一个运行远程服务的服务器
一个RMI 命名服务,它允许客户端去发现这个远程服务
类文件的提供者(一个HTTP或者FTP服务器)
一个需要这个远程服务的客户端程序
/**
*定义一个远程接口,必须继承Remote接口,其中需要远程调用的方法必须抛出RemoteException异常
*/
publicinterface IHello extends Remote {
public String helloWorld() throwsRemoteException;
public String sayHelloToSomeBody(StringsomeBodyName) throws RemoteException;
}
publicclass HelloImpl extends UnicastRemoteObject implements IHello {
/**
* 因为UnicastRemoteObject的构造方法抛出了RemoteException异常,因此这里默认的构造方法必须写,必须声明抛出RemoteException异常
*/
public HelloImpl() throws RemoteException {
}
public String helloWorld() throwsRemoteException {
return "Hello World!";
}
public String sayHelloToSomeBody(StringsomeBodyName) throws RemoteException {
return "你好,"+ someBodyName + "!";
}
}
/**
*创建RMI注册表,启动RMI服务,并将远程对象注册到RMI注册表中。
*/
publicclass HelloServer {
public static void main(String args[]) {
try {
//创建一个远程对象
IHello rhello = new HelloImpl();
//本地主机上的远程对象注册表Registry的实例,并指定端口为8888,这一步必不可少(Java默认端口是 1099),必不可缺的一步,缺少注册表创建,则无法绑定对象到远程注册表上
LocateRegistry.createRegistry(8888);
//把远程对象注册到RMI注册服务器上,并命名为 RHello
//绑定的URL标准格式为:rmi: //host:port/name(其中协议名可以省略,下面两种写法都是正确的)
Naming.bind("rmi://localhost:8888/RHello",rhello);
// Naming.bind("//localhost:8888/RHello",rhello);
} catch (RemoteException e) {
} catch (AlreadyBoundException e) {
} catch (MalformedURLException e) {
}
}
}
/**
*客户端测试,在客户端调用远程对象上的远程方法,并返回结果。
*/
publicclass HelloClient {
public static void main(String args[]){
try {
//在 RMI服务注册表中查找名称为RHello的对象,并调用其上的方法
IHello rhello =(IHello)Naming.lookup("rmi://localhost:8888/RHello");
System.out.println(rhello.helloWorld());
System.out.println(rhello.sayHelloToSomeBody("熔岩"));
} catch (NotBoundException e) {
} catch (MalformedURLException e) {
} catch (RemoteException e) {
}
}
}
RMI socket的网络编程主要有以下几个方面:
第一、RMI是面向对象的,而后者不是。
第二、RMI是与语言相绑定的。比如当你使用Java RMI技术的时候,客户端与服务器端都必须使用Java开发。而socket的网络编程是使用独立于开发语言的,甚至独立于平台。基于socket的网络编程,客户端与服务器端可以使用不同开发语言和不同的平台。
第三、从网络协议栈的观点来看,RMI与socket的网络编程处于不同层次上。基于socket的网络编程位于TCP/UDP协议之上,而RMI在 TCP协议之上,又定义了自己的应用协议,其传输层采用的是Java远程方法协议(JRMP)。可见,在网络协议栈上,基于RMI的应用位置更高一些,这也决定了,与 socket的网络编程相比,RMI会丧失一些灵活性和可控性,但是好处是它带给了应用开发者更多的简洁,方便和易用。比如:如果你用的是RMI,你不需要关心消息是怎么序列化的,你只需要像本地方法调用一样,使用RMI。代价是:应用开发者无法很好地控制消息的序列化机制。
RMI目前使用Java远程消息交换协议JRMP(JavaRemote Messaging Protocol)进行通信。JRMP是专为Java的远程对象制定的协议。用Java RMI开发的应用系统可以部署在任何支持JRE(Java Run Environment Java,运行环境)的平台上。但由于JRMP是专为Java对象制定的,因此,RMI对于用非Java语言开发的应用系统的支持不足。不能与用非 Java语言书写的对象进行通信。
第四、这是最后一点不同,就是两种方法的性能比较,其往往决定着你将使用那种技术来开发你的应用。
实验的结果是:RMI与TCP based socket相比,传输相同的有效数据,RMI需要占用更多的网络带宽(protocol overhead)。从这里,我们可以得出一个一般性的结论:RMI主要是用于远程方法的”调用“(RMI是多么的名符其实:)),其技术内涵强调的是 “调用”,基于此,我能想到的是:移动计算,和远程控制,当你的应用不需要在client与server之间传输大量的数据时,RMI是较好的选择,它简洁、易于开发。但是,一旦你的应用需要在client与server之间传输大量的数据,极端的,比如FTP应用,则RMI是不适合的,我们应该使用 socket。