title: rmi-iiop
date: 2019-3-9
tags: [中间件,远程方法调用]
categories: 中间件结构
rmi
rmi结构
一、RMI
rmi结构
rmi即远程方法调用,字面意思就是可以在客户机上远程调用服务器上的方法。
![2869373-95d9321f0afa5e13.jpg](https://i-blog.csdnimg.cn/blog_migrate/6a4669d66a468e5022133d1c57440d03.webp?x-image-process=image/format,png)
rmi
可以分成三部分:第一层是存根(Stubs)和骨架(Skeletons),第二层是引用层,用来通过名字寻找对象,第三层是传输层,用于依赖于TCP/IP的客户机和服务机的通讯。
rmi流程
- 客户端调用远程方法,首先 Stubs 负责将调用的远程方法名和传入参数打包。
- 客户端通过引用层找到目标服务器,并通过传输层将数据传送到服务器。
- 服务器接受到数据,通过引用层,Skeletons解析远程方法名和传入参数。
- 服务器调用远程方法。
- 服务器将返回值或异常值沿相反方向返回。
- 客户端的 Stubs 解析返回数据。
![2869373-1d112f533bfebcb2.png](https://i-blog.csdnimg.cn/blog_migrate/da376ba741094c0519c681086c22eebb.webp?x-image-process=image/format,png)
实现的RMI的步骤
1.定义远程接口
远程对象需要由实现远程接口实现。远程接口继承于java.rmi.Remote
,然后在接口内声明远程函数,要注意远程函数需要抛出java.rmi.RemoteException
异常,用于调用远程函数失败时使用。
package example.hello;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Hello extends Remote {
String sayHello() throws RemoteException;
}
2.实现服务器
在服务器的主函数中。主要完成了两个任务。①实现远程对象的接口并导出为存根;②将远程对象的存根注册到java rmi中。
package example.hello;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class Server implements Hello {
public void Server() {}
public String sayHello() {
return "Hello, world!";
}
public static void main(String args[]) {
try {
Server obj = new Server();
Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 0);
// Bind the remote object's stub in the registry
Registry registry = LocateRegistry.getRegistry();
registry.bind("Hello", stub);
System.err.println("Server ready");
} catch (Exception e) {
System.err.println("Server exception: " + e.toString());
e.printStackTrace();
}
}
}
在本例中,将服务器自身实现为远程对象。这样就在main
函数中直接调用;将远程对象和服务器分开实现可以。
UnicastRemoteObject.exportObject
这个静态方法导出了所需的远程对象,用以接收来自未知TCP端口的远程方法调用,并返回了一个用于传输远程对象给客户端的存根(stub);在执行完这个函数之后,java rmi 运行时将会在一个新的socket或者一个共享的socket中监听远程函数调用;stub中保存了主机名、端口以便联络远程对象。
对于客户端来说,要调用远程对象的方法,就必须知道远程对象的stub,所以服务器中将一个name和stub通过 registry.bind
函数绑定起来。
本例中远程对象在实现函数sayHello()
时并不需要抛出异常,因为这个函数本身的实现时不抛出任何异常的。
3.实现客户端
准备工作都做好了,使用起来就方便多了;在客户端中只需要在registry中通过name找到stub,然后调用远程方法就可以了。
package example.hello;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Client {
private void Client() {}
public static void main(String[] args) {
String host = (args.length < 1) ? null : args[0];
try {
Registry registry = LocateRegistry.getRegistry(host);
Hello stub = (Hello) registry.lookup("Hello");
String response = stub.sayHello();
System.out.println("response: " + response);
} catch (Exception e) {
System.err.println("Client exception: " + e.toString());
e.printStackTrace();
}
}
}
LocateRegistry.getRegistry(host)
函数表示指定主机名的registry,host
为空表示使用本地的主机地址。
4.运行
Server exception: java.rmi.ConnectException: Connection refused to host: 192.168.44.1; nested exception is:
java.net.ConnectException: Connection refused: connect
java.rmi.ConnectException: Connection refused to host: 192.168.44.1; nested exception is:
java.net.ConnectException: Connection refused: connect
at sun.rmi.transport.tcp.TCPEndpoint.newSocket(Unknown Source)
at sun.rmi.transport.tcp.TCPChannel.createConnection(Unknown Source)
···
解决方法:确保在server启动之前,通过start rmiregistry
启动rmiregistry
。
Server exception: java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
java.lang.ClassNotFoundException: com.whut.distributedTelephoneDirectory.Hello
java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
java.lang.ClassNotFoundException: com.whut.distributedTelephoneDirectory.Hello···
解决方法:异常说明没有找到Hello类,确保rmiregistry
已经在工程的bin(编译输出文件夹)目录下启动。
二 iiop
Internet Inter-ORB Protocol (IIOP)互联网内部对象请求代理协议,RMI-IIOP 将CORBA的功能添加到java-rmi中,为很多其他语言平台提供了互操作性和连沟通性; RMI-IIOP使java应用可以透明的调用远程网络服务提供的操作,并且这个操作使用了iiop工业标准。
RMI-IIOP可以让使用rmi编程的java程序员通过iiop来传输,RMI-IIOP为使用其他语言实现CORBA对象提供了互操作性,并且所有远程接口都可以只使用rmi进行定义。
实现的RMI的步骤
1.定义远程类的接口
//HelloInterface.java
import java.rmi.Remote;
public interface HelloInterface extends java.rmi.Remote {
public void sayHello( String from ) throws java.rmi.RemoteException;
}
2. 定义实现类
//HelloImpl.java
import javax.rmi.PortableRemoteObject;
public class HelloImpl extends PortableRemoteObject implements HelloInterface {
public HelloImpl() throws java.rmi.RemoteException {
super(); // invoke rmi linking and remote object initialization
}
public void sayHello( String from ) throws java.rmi.RemoteException {
System.out.println( "Hello from " + from + "!!" );
System.out.flush();
}
}
远程类实现接口并继承于PortableRemoteObject
,可以让远程来使用基于IIOP传输的协议。
要注意构造函数要调用父类的构造函数PortableRemoteObject
来导出远程对象,同时构造函数要抛出RemoteException
异常,因为如果网络不通畅可能导致无法导出远程对象而产生异常。
3.定义服务器
//HelloServer.java
import javax.naming.InitialContext;
import javax.naming.Context;
public class HelloServer {
public static void main(String[] args) {
try {
// Step 1: Instantiate the Hello servant
HelloImpl helloRef = new HelloImpl();
// Step 2: Publish the reference in the Naming Service
// using JNDI API
// Properties props = new Properties();
// props.setProperty("java.naming.factory.initial","com.sun.jndi.cosnaming.CNCtxFactory");
// props.setProperty("java.naming.provider.url", "iiop://localhost:2809");
//
// Context initialNamingContext = new InitialContext(props);
Context initialNamingContext = new InitialContext();
initialNamingContext.rebind("HelloService", helloRef );
System.out.println("Hello Server: Ready...");
} catch (Exception e) {
System.out.println("Trouble: " + e);
e.printStackTrace();
}
}
}
4.定义客户端
//HelloClient.java
import java.rmi.RemoteException;
import java.net.MalformedURLException;
import java.rmi.NotBoundException;
import javax.rmi.*;
import java.util.Vector;
import javax.naming.NamingException;
import javax.naming.InitialContext;
import javax.naming.Context;
public class HelloClient {
public static void main( String args[] ) {
Context ic;
Object objref;
HelloInterface hi;
try {
//Properties props = new Properties();
//props.setProperty("java.naming.factory.initial","com.sun.jndi.cosnaming.CNCtxFactory");
//props.setProperty("java.naming.provider.url", "iiop://localhost:2809");
//ic = new InitialContext(props);
ic = new InitialContext();
// STEP 1: Get the Object reference from the Name Service
// using JNDI call.
objref = ic.lookup("HelloService");
System.out.println("Client: Obtained a ref. to Hello server.");
// STEP 2: Narrow the object reference to the concrete type and
// invoke the method.
hi = (HelloInterface) PortableRemoteObject.narrow(
objref, HelloInterface.class);
hi.sayHello( " MARS " );
} catch( Exception e ) {
System.err.println( "Exception " + e + "Caught" );
e.printStackTrace( );
return;
}
}
}
5.编译运行
首先将所有代码编译,然后要在生成java文件的路径bin
下使用rmic
来生成skeletons
和 stubs
。
rmic -iiop HelloImpl
然后继续在该路径在开启Naming Service。
start orbd -ORBInitialPort 2809
然后就可以依次启动服务器和客户端了。
reference:
http://blog.jobbole.com/104655/
http://zhuzhucs.com/2017/05/30/RMI-Helloworld/
http://zhuanlan.51cto.com/art/201806/576009.htm
http://jm.taobao.org/2018/06/13/%E5%BA%94%E7%94%A8/
https://docs.oracle.com/javase/6/docs/technotes/guides/rmi/hello/hello-world.html