RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
基本简介
工作原理
协议结构
一、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<String> params = new Vector<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(); } } }
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.jsp</welcome-file> </welcome-file-list> <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> </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.
写 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 : int n2), //defines a method
|
7 |
} |
编译Thrift定义文件
下面的命令编译.thrift文件
1 |
thrift --gen <language> <thrift filename= "" > </thrift></language>
|
对于我的例子来讲,命令是:
1 |
thrift --gen java add.thrift |
在执行完代码后,在gen-java目录下你会发现构建RPC服务器和客户端有用的源代码。在我的例子中我将创建一个叫做AddtionService.java的java文件。
写一个 service handler
Service handler 类必须实现 AdditionService.Iface接口。
示例Service handler(AdditionServiceHandler.java)
01 |
package com.eviac.blog.samples.thrift.server;
|
02 |
|
03 |
import org.apache.thrift.TException;
|
04 |
|
05 |
public class AdditionServiceHandler implements AdditionService.Iface {
|
06 |
|
07 |
@Override |
08 |
public int add( int n1, int n2) throws TException {
|
09 |
return n1 + n2;
|
10 |
}
|
11 |
|
12 |
} |
写一个简单的服务器
下面的示例代码是一个简单的Thrift服务器。可以看到下面的代码中有一段是注释了的,可以去掉注释来启用多线程服务器。
示例服务器(MyServer.java)
01 |
package com.eviac.blog.samples.thrift.server;
|
02 |
|
03 |
import org.apache.thrift.transport.TServerSocket;
|
04 |
import org.apache.thrift.transport.TServerTransport;
|
05 |
import org.apache.thrift.server.TServer;
|
06 |
import org.apache.thrift.server.TServer.Args;
|
07 |
import org.apache.thrift.server.TSimpleServer;
|
08 |
|
09 |
public class MyServer {
|
10 |
|
11 |
public static void StartsimpleServer(AdditionService.Processor<additionservicehandler> processor) {
|
12 |
try {
|
13 |
TServerTransport serverTransport = new TServerSocket( 9090 );
|
14 |
TServer server = new TSimpleServer(
|
15 |
new Args(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 void main(String[] args) {
|
29 |
StartsimpleServer( new AdditionService.Processor<additionservicehandler>( new AdditionServiceHandler()));
|
30 |
}
|
31 |
|
32 |
}</additionservicehandler></additionservicehandler> |
写一个客户端
下面的例子是一个使用Java写的客户端短使用AdditionService的服务。
01 |
package com.eviac.blog.samples.thrift.client;
|
02 |
|
03 |
import org.apache.thrift.TException;
|
04 |
import org.apache.thrift.protocol.TBinaryProtocol;
|
05 |
import org.apache.thrift.protocol.TProtocol;
|
06 |
import org.apache.thrift.transport.TSocket;
|
07 |
import org.apache.thrift.transport.TTransport;
|
08 |
import org.apache.thrift.transport.TTransportException;
|
09 |
|
10 |
public class AdditionClient {
|
11 |
|
12 |
public static void main(String[] args) {
|
13 |
|
14 |
try {
|
15 |
TTransport transport;
|
16 |
|
17 |
transport = new TSocket( "localhost" , 9090 );
|
18 |
transport.open();
|
19 |
|
20 |
TProtocol protocol = new TBinaryProtocol(transport);
|
21 |
AdditionService.Client client = new AdditionService.Client(protocol);
|
22 |
|
23 |
System.out.println(client.add( 100 , 200 ));
|
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如何使用以及好处:
- public class MainClient {
- public static void main(String[] args) {
- Echo echo = RPC.getProxy(Echo.class, "127.0.0.1", 20382);
- System.out.println(echo.echo("hello,hello"));
- }
- }
- public interface Echo {
- public String echo(String string);
- }
使用RPC.getProxy生成接口Echo的代理实现类。然后就可以像使用本地的程序一样来调用Echo中的echo方法。
使用RPC的好处是简化了远程服务访问。提高了开发效率。在分发代码时,只需要将接口分发给客户端使用,在客户端看来只有接口,没有具体类实现。这样保证了代码的可扩展性和安全性。
在看了RPCClient如何使用,我们再来定义一个RPC服务器的接口,看看服务器都提供什么操作:
- public interface Server {
- public void stop();
- public void start();
- public void register(Class interfaceDefiner,Class impl);
- public void call(Invocation invo);
- public boolean isRunning();
- public int getPort();
- }
服务器提供了start和stop方法。使用register注册一个接口和对应的实现类。call方法用于执行Invocation指定的接口的方法名。isRunning返回了服务器的状态,getPort()则返回了服务器使用的端口。
来看看Invocation的定义:
- public class Invocation implements Serializable{
- /**
- *
- */
- private static final long serialVersionUID = 1L;
- private Class interfaces;
- private Method method;
- private Object[] params;
- private Object result;
- /**
- * @return the result
- */
- public Object getResult() {
- return result;
- }
- /**
- * @param result the result to set
- */
- public void setResult(Object result) {
- this.result = result;
- }
- /**
- * @return the interfaces
- */
- public Class getInterfaces() {
- return interfaces;
- }
- /**
- * @param interfaces the interfaces to set
- */
- public void setInterfaces(Class interfaces) {
- this.interfaces = interfaces;
- }
- /**
- * @return the method
- */
- public Method getMethod() {
- return method;
- }
- /**
- * @param method the method to set
- */
- public void setMethod(Method method) {
- this.method = method;
- }
- /**
- * @return the params
- */
- public Object[] getParams() {
- return params;
- }
- /**
- * @param params the params to set
- */
- public void setParams(Object[] params) {
- this.params = params;
- }
- @Override
- public String toString() {
- return interfaces.getName()+"."+method.getMethodName()+"("+Arrays.toString(params)+")";
- }
- }
具体服务器实现类中的call方法是这样使用Invocation的:
- @Override
- public void call(Invocation invo) {
- Object obj = serviceEngine.get(invo.getInterfaces().getName()); //根据接口名,找到对应的处理类
- if(obj!=null) {
- try {
- Method m = obj.getClass().getMethod(invo.getMethod().getMethodName(), invo.getMethod().getParams());
- Object result = m.invoke(obj, invo.getParams());
- invo.setResult(result);
- } catch (Throwable th) {
- th.printStackTrace();
- }
- } else {
- throw new IllegalArgumentException("has no these class");
- }
- }
下面来看服务器接收连接并处理连接请求的核心代码:
- public class Listener extends Thread {
- private ServerSocket socket;
- private Server server;
- public Listener(Server server) {
- this.server = server;
- }
- @Override
- public void run() {
- System.out.println("启动服务器中,打开端口" + server.getPort());
- try {
- socket = new ServerSocket(server.getPort());
- } catch (IOException e1) {
- e1.printStackTrace();
- return;
- }
- while (server.isRunning()) {
- try {
- Socket client = socket.accept();
- ObjectInputStream ois = new ObjectInputStream(client.getInputStream());
- Invocation invo = (Invocation) ois.readObject();
- server.call(invo);
- ObjectOutputStream oos = new ObjectOutputStream(client.getOutputStream());
- oos.writeObject(invo);
- oos.flush();
- oos.close();
- ois.close();
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- try {
- if (socket != null && !socket.isClosed())
- socket.close();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
RPC具体的Server类是这样来使用Listener的:
- public static class RPCServer implements Server{
- private int port = 20382;
- private Listener listener;
- private boolean isRuning = true;
- /**
- * @param isRuning the isRuning to set
- */
- public void setRuning(boolean isRuning) {
- this.isRuning = isRuning;
- }
- /**
- * @return the port
- */
- public int getPort() {
- return port;
- }
- /**
- * @param port the port to set
- */
- public void setPort(int port) {
- this.port = port;
- }
- private Map<String ,Object> serviceEngine = new HashMap<String, Object>();
- @Override
- public void call(Invocation invo) {
- System.out.println(invo.getClass().getName());
- Object obj = serviceEngine.get(invo.getInterfaces().getName());
- if(obj!=null) {
- try {
- Method m = obj.getClass().getMethod(invo.getMethod().getMethodName(), invo.getMethod().getParams());
- Object result = m.invoke(obj, invo.getParams());
- invo.setResult(result);
- } catch (Throwable th) {
- th.printStackTrace();
- }
- } else {
- throw new IllegalArgumentException("has no these class");
- }
- }
- @Override
- public void register(Class interfaceDefiner, Class impl) {
- try {
- this.serviceEngine.put(interfaceDefiner.getName(), impl.newInstance());
- System.out.println(serviceEngine);
- } catch (Throwable e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- @Override
- public void start() {
- System.out.println("启动服务器");
- listener = new Listener(this);
- this.isRuning = true;
- listener.start();
- }
- @Override
- public void stop() {
- this.setRuning(false);
- }
- @Override
- public boolean isRunning() {
- return isRuning;
- }
- }
服务器端代码搞定后,来看看客户端的代码,先看看我们刚开始使用RPC.getProxy方法:
- public static <T> T getProxy(final Class<T> clazz,String host,int port) {
- final Client client = new Client(host,port);
- InvocationHandler handler = new InvocationHandler() {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- Invocation invo = new Invocation();
- invo.setInterfaces(clazz);
- invo.setMethod(new org.jy.rpc.protocal.Method(method.getName(),method.getParameterTypes()));
- invo.setParams(args);
- client.invoke(invo);
- return invo.getResult();
- }
- };
- T t = (T) Proxy.newProxyInstance(RPC.class.getClassLoader(), new Class[] {clazz}, handler);
- return t;
- }
Client类的代码如下:
- public class Client {
- private String host;
- private int port;
- private Socket socket;
- private ObjectOutputStream oos;
- private ObjectInputStream ois;
- public String getHost() {
- return host;
- }
- public void setHost(String host) {
- this.host = host;
- }
- public int getPort() {
- return port;
- }
- public void setPort(int port) {
- this.port = port;
- }
- public Client(String host, int port) {
- this.host = host;
- this.port = port;
- }
- public void init() throws UnknownHostException, IOException {
- socket = new Socket(host, port);
- oos = new ObjectOutputStream(socket.getOutputStream());
- }
- public void invoke(Invocation invo) throws UnknownHostException, IOException, ClassNotFoundException {
- init();
- System.out.println("写入数据");
- oos.writeObject(invo);
- oos.flush();
- ois = new ObjectInputStream(socket.getInputStream());
- Invocation result = (Invocation) ois.readObject();
- invo.setResult(result.getResult());
- }
- }
至此,RPC的客户端和服务器端代码完成,启动服务器的代码如下:
- public class Main {
- public static void main(String[] args) {
- Server server = new RPC.RPCServer();
- server.register(Echo.class, RemoteEcho.class);
- server.start();
- }
- }
现在先运行服务器端代码,再运行客户端代码,就可以成功运行。
详细的代码,参考附件的源代码。
在写这个RPC时,没有想太多。在数据串行化上,使用了java的标准io序列化机制,虽然不能跨平台,但是做DEMO还是不错的;另外在处理客户端请求上,使用了ServerSocket,而没有使用ServerSocketChannel这个java nio中的新特性;在动态生成接口的实现类上,使用了java.lang.reflet中的Proxy类。他可以动态创建接口的实现类。
主要利用socket通信,反射,代理实现类似RMI的RPC框架
首先是框架的代码
- package framework;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import java.net.ServerSocket;
- import java.net.Socket;
- /**
- * RpcFramework
- *
- * @author william.liangf
- */
- public class RpcFramework {
- /**
- * 暴露服务
- *
- * @param service 服务实现
- * @param port 服务端口
- * @throws Exception
- */
- public static void export(final Object service, int port) throws Exception {
- if (service == null)
- throw new IllegalArgumentException("service instance == null");
- if (port <= 0 || port > 65535)
- throw new IllegalArgumentException("Invalid port " + port);
- System.out.println("Export service " + service.getClass().getName() + " on port " + port);
- ServerSocket server = new ServerSocket(port);
- for(;;) {
- try {
- final Socket socket = server.accept();//服务器端一旦收到消息,就创建一个线程进行处理
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- try {
- ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
- try {
- String methodName = input.readUTF();//service是服务器端提供服务的对象,但是,要通过获取到的调用方法的名称,参数类型,以及参数来选择对象的方法,并调用。获得方法的名称
- Class<?>[] parameterTypes = (Class<?>[])input.readObject();//获得参数的类型
- Object[] arguments = (Object[])input.readObject();//获得参数
- ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
- try {
- Method method = service.getClass().getMethod(methodName, parameterTypes);//通过反射机制获得方法
- Object result = method.invoke(service, arguments);//通过反射机制获得类的方法,并调用这个方法
- output.writeObject(result);//将结果发送
- } catch (Throwable t) {
- output.writeObject(t);
- } finally {
- output.close();
- }
- } finally {
- input.close();
- }
- } finally {
- socket.close();
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }).start();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- /**
- * 引用服务
- *
- * @param <T> 接口泛型
- * @param interfaceClass 接口类型
- * @param host 服务器主机名
- * @param port 服务器端口
- * @return 远程服务
- * @throws Exception
- *///原理是通过代理,获得服务器端接口的一个“代理”的对象。对这个对象的所有操作都会调用invoke函数,在invoke函数中,是将被调用的函数名,参数列表和参数发送到服务器,并接收服务器处理的结果
- @SuppressWarnings("unchecked")
- public static <T> T refer(final Class<T> interfaceClass, final String host, final int port) throws Exception {
- if (interfaceClass == null)
- throw new IllegalArgumentException("Interface class == null");
- if (! interfaceClass.isInterface())
- throw new IllegalArgumentException("The " + interfaceClass.getName() + " must be interface class!");
- if (host == null || host.length() == 0)
- throw new IllegalArgumentException("Host == null!");
- if (port <= 0 || port > 65535)
- throw new IllegalArgumentException("Invalid port " + port);
- System.out.println("Get remote service " + interfaceClass.getName() + " from server " + host + ":" + port);
- return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[] {interfaceClass}, new InvocationHandler() {
- public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
- Socket socket = new Socket(host, port);
- try {
- ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
- try {
- output.writeUTF(method.getName());
- output.writeObject(method.getParameterTypes());
- output.writeObject(arguments);
- ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
- try {
- Object result = input.readObject();
- if (result instanceof Throwable) {
- throw (Throwable) result;
- }
- return result;
- } finally {
- input.close();
- }
- } finally {
- output.close();
- }
- } finally {
- socket.close();
- }
- }
- });
- }
- }
服务接口
- package user;
- public interface HelloService {
- String hello(String name);
- }
实现服务
- package user;
- public class HelloServiceImpl implements HelloService{
- public String hello(String name) {
- return "Hello " + name;
- }
- }
服务器
- package user;
- import framework.RpcFramework;
- public class Server {
- public static void main(String []args) throws Exception {
- HelloService service = new HelloServiceImpl();
- RpcFramework.export(service, 1234);
- }
- }
客户机
- package user;
- import framework.RpcFramework;
- public class Client {
- public static void main(String[] args) throws Exception {
- HelloService service = RpcFramework.refer(HelloService.class, "127.0.0.1", 1234);
- for (int i = 0; i < Integer.MAX_VALUE; i ++) {
- String hello = service.hello("World" + i);
- System.out.println(hello);
- Thread.sleep(1000);
- }
- }
- }
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.add</methodName>
<params>
<param>
<value>
<i4>2</i4>
</value>
</param>
<param>
<value>
<i4>3</i4>
</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>5</i4>
</value>
</param>
</params>
</methodResponse>
其格式很简单,几乎是不言自明的,分别用methodCall和methodResponse标签标识发送给Server的调用请求和Server的返回结果,请求方法的名称用methodName标识,参数用params和param标识,而参数的类型标签则如下表所示:
Tag |
Java Type |
说明 |
<i4> or <int> |
Integer/int |
4字节带符号整数值 |
<boolean> |
Boolean |
0 (false) or 1 (true) |
<string> |
String |
字符串 |
<double> |
Double |
双精度带符号浮点值 |
<dateTime.iso8601> |
java.util.Date |
日期/时间 |
<base64> |
byte[] |
base64编码的二进制数据 |
<struct> |
java.util.Map |
键值对,键为String类型,而值为任意有效类型 |
<array> |
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<String> params = new Vector<String>();
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>XmlRpcServer</servlet-name>
<servlet-class>demo.xmlrpc.Server1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>XmlRpcServer</servlet-name>
<url-pattern>/XmlRpcServer</url-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<String> params = new Vector<String>();
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<String> params = new Vector<String>(); // 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应用的工具。它的特点是:
-
100%的纯Java开发
-
完整的客户端功能,包括portmapper的访问。
-
完整的服务器端功能。
-
有为.x文件设计的纯Java协议编译工具,与rpcgen兼容。
-
基于Java的portmapper。
-
开源的代码;文档支持。
下载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<MAXNAMELEN>; /* 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系列后,本想接着写写其它几种典型的远程通信协议:RMI、XML-RPC、SOAP,但由于工作的原因,加之房子装修等麻烦事,一直没有心情动笔。今天接到装修公司老板电话说开工证要晚几天办下来,要停工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、TCP/IP等,定义了数据如何在网络中传输。 - 映射机制:将客户端的调用映射到服务器端的实际方法执行。 4. **RPC框架的类型**: - gRPC:基于HTTP/2,使用Protocol Buffers作为数据交换格式...
在这个特定的压缩包文件"PHPRPC远程过程调用协议(ASP).7z"中,我们可以推测它包含了与PHPRPC协议相关的源代码,特别地,是针对ASP(Active Server Pages)环境的实现。 首先,我们需要理解远程过程调用(RPC)的...
### 远程过程调用(RPC):概念与实践 #### 概述 远程过程调用(Remote Procedure Call,简称RPC)是一种通过网络调用一个应用程序的方法,它使得客户端能够像调用本地过程那样调用远程服务器上的过程,从而简化了...
以前在学习DOS命令的时候整理出来的笔记,上传...七.RPC:远程过程调用协议 八.关于Netsh上下文的说明 九.关于boot.ini文件里的/noexecute=optin 十.SYS——系统复制命令 十一. MS-DOS提供的标准设备驱动程序详解
在提供的压缩包“PHPRPC远程过程调用协议.7z”中,可能包含了以下内容: 1. **源代码**:这通常包括PHPRPC的服务器端和客户端的PHP源码。服务器端代码可能包含一个或多个PHP类,这些类定义了可供远程调用的函数。...
远程过程调用(RPC)是一种计算机通信协议,它允许一个程序调用另一个在不同地址空间(可能在同一台机器上,也可能在远程网络上的另一台机器上)运行的程序。PHP RPC是PHP实现的RPC框架,它简化了在分布式系统中进行...
RPC,即Remote Procedure Call(远程过程调用),是计算机网络编程中的一个重要概念,它允许一个程序在不理解底层网络协议的情况下,调用另一个网络上不同机器上的程序。在这个"RPC.rar"压缩包中,主要围绕C++语言在...
**远程过程调用协议(RPC)详解** 远程过程调用(Remote Procedure Call,简称RPC)是一种计算机通信协议,它允许程序在不同的网络节点上执行远程的函数或子程序,而无需程序员关注底层网络协议和细节。RFC1050是...
PHPRPC是一个轻型的、安全的、跨网际的、跨语言的、跨平台的、跨环境的、跨域的、支持复杂对象传输的、支持引用参数传递的、支持内容输出重定向的、支持分级错误处理的、支持会话的、面向服务的高性能远程过程调用...
PHPRPC是一个轻型的、安全的、跨网际的、跨语言的、跨平台的、跨环境的、跨域的、支持复杂对象传输的、支持引用参数传递的、支持内容输出重定向的、支持分级错误处理的、支持会话的、面向服务的高性能远程过程调用...
协议层: Protocol Protocol ------------------------------------------------------ 传输层: Transport <-----------> Transport ------------------------------------------------------ Expo
3. **RPC协定**:定义了一个简单的协议来执行远程过程调用和响应,允许客户端指定远程方法名、传递参数并接收服务器的响应。 4. **传输和协议绑定**:定义了如何通过底层协议传输SOAP消息。 #### 示例:将XML文档...
RPC的核心思想是透明性,即客户端可以像调用本地方法一样调用远程服务器上的方法,而无需关心网络通信的细节。 首先,RPC框架主要包括以下几个关键组件: 1. **客户端(Client)**:发起RPC请求的程序,它调用远程...
RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,它允许一个程序请求另一个地址空间(通常是远程计算机上的程序)执行代码,并等待结果返回。RPC使得程序能够像调用本地函数一样调用位于远程...
远程过程调用协议(RPC,Remote Procedure Call)是一种网络通信协议,允许一台计算机上的程序(客户端)通过网络请求另一台计算机上的程序(服务器)执行特定的功能,并返回结果。RFC1050是Sun Microsystems公司在...
RPC(Remote Procedure Call)远程过程调用是一种计算机通信协议,允许一个程序在某处执行,通过网络调用另一台计算机上的程序,就像它是一个本地函数一样。这种技术在分布式系统和微服务架构中广泛使用,简化了不同...
RPC,即Remote Procedure Call Protocol,远程过程调用协议,是一种在分布式系统中实现客户端与服务器端通信的技术。这种协议允许一个程序(客户端)在不理解底层网络细节的情况下,调用运行在另一台计算机(服务器...
### RPC远程过程调用详解 #### 一、引言 远程过程调用(Remote Procedure Call, RPC)是一种网络通信模式,它允许程序通过网络调用位于另一台计算机上的子程序,如同调用本地程序一样。RPC的设计目标是提供一个透明...
### 使用RPC机制将本地调用转换为远程调用 #### 概述 远程过程调用(RPC, Remote Procedure Call)是一种通信协议,它允许在一台计算机上的程序调用另一台计算机上的子程序或函数,而无需程序员明确了解底层通信...
XML_RPC是一种简单且灵活的远程过程调用协议,它使用HTTP作为传输层,XML作为数据交换格式。这种技术使得移动设备能够像本地调用一样,跨网络调用远程服务器上的方法,从而实现移动博客发布器的功能。 首先,我们...