RCP
直接看RMI感觉有点吃力,所以来看看RPC先预热一下。
RPC(Remote Procedure Call) 远程过程调用,这只是一个统称,重点在于方法调用(不支持对象的概念),具体实现甚至可以用RMI、RestFul等去实现,但一般不用,因为RMI不能跨语言,而RestFul效率太低。
多用于服务器集群间的通信,因此常使用更加高效短小精悍的传输模式以提高效率。
先看看B站马士兵的这个视频来了解RPC的演练过程,以及底层的原理:https://www.bilibili.com/video/BV1zE41147Zq?from=search&seid=13740626242455157002
RMI篇(1)
RMI全称是Remote Method Invocation,远程方法调用。从这个名字就可以看出,他的目标和RPC(Remote Procedure Call 远程过程调用)其实是类似的,是让某个Java虚拟机上的对象调用另一个Java虚拟机中对象上的方法,只不过RMI是Java独有的一种机制。
此处引用文章JAVA RMI 原理和使用浅析的一张图演示RMI的交互过程:
通俗来讲,便是RMI Registry作为client和server的中间人,假设服务端是商品仓库,客户端是购买者,则RMI Registry就是中介,只负责告诉客户可以售卖的商品相关信息。仓库在中介处登记可售商品,客户从中介处查看可购买的商品。客户从中介处获得商品的相关信息,然后交给跑腿(存根stub)去跟仓库人员(骨架skeleton)取货,双方商品信息确认一致,进行交易,客户获得商品。
下面,我们直接从一个例子开始演示RMI的流程。
首先编写一个RMI Server:
/*
* 在Java中,只要一个类extends了java.rmi.Remote接口,即可成为存在于服务器端的远程对象,
* 供客户端访问并提供一定的服务。JavaDoc描述:Remote 接口用于标识其方法可以从非本地虚拟机上
* 调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口”
* (扩展 java.rmi.Remote 的接口)中指定的这些方法才可被远程调用。
*/
public class RMIServer {
/* extends了Remote接口的类或者其他接口中的方法若是声明抛出了RemoteException异常,
* 则表明该方法可被客户端远程访问调用。
*/
public interface IRemoteHelloWorld extends Remote {
public String hello() throws RemoteException;
}
//java.rmi.server.UnicastRemoteObject构造函数中将生成stub和skeleton
public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld {
//这个实现必须有一个显式的构造函数,并且要抛出一个RemoteException异常
protected RemoteHelloWorld() throws RemoteException {
super();
}
@Override
public String hello() throws RemoteException {
System.out.println("call from");
return "Hello World!";
}
}
/* 注册远程对象,向客户端提供远程对象服务
* 远程对象是在远程服务上创建的,你无法确切地知道远程服务器上的对象的名称
* 但是,将远程对象注册到RMI Service之后,客户端就可以通过RMI Service请求
* 到该远程服务对象的stub了,利用stub代理就可以访问远程服务对象了
*/
private void start() throws Exception {
/* 生成stub和skeleton,并返回stub代理引用 */
RemoteHelloWorld h = new RemoteHelloWorld();
/* 本地创建并启动RMI Service,被创建的Registry服务将在指定的端口上侦听到来的请求
* 实际上,RMI Service本身也是一个RMI应用,我们也可以从远端获取Registry:
* public interface Registry extends Remote;
* public static Registry getRegistry(String host, int port) throws RemoteException;
*/
LocateRegistry.createRegistry(1099);
/* 将stub代理绑定到Registry服务的URL上 */
Naming.rebind("rmi://127.0.0.1:1099/Hello", h);
}
public static void main(String[] args) throws Exception {
new RMIServer().start();
}
}
一个RMI Server分为三部分:
1.编写远程服务接口,该接口必须继承java.rmi.Remote
接口,方法必须抛出java.rmi.RemoteException
异常,其中定义我们要远程调用的函数,比如这里的hello()
。
2.编写远程接口实现类,该实现类必须继承java.rmi.server.UnicastRemoteObject
类。
3.一个主类,用来创建Registry,并将上面的类实例化后绑定到一个地址。这就是我们所谓的Server了。
这里是简略的写法,将三个部分放在了一个类里。
接着我们编写一个RMI Client:
public class RMIClient {
public static void main(String[] args) throws Exception {
/* 从RMI Registry中请求stub
* 如果RMI Service就在本地机器上,URL就是:rmi://localhost:1099/hello
* 否则,URL就是:rmi://RMIService_IP:1099/hello
*/
RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld) Naming.lookup("rmi://192.168.78.128/Hello");
/* 通过stub调用远程接口实现 */
String ret = hello.hello();
System.out.println(ret);
}
}
客户端就简单多了,使用Naming.lookup
在Registry中寻找到名字是Hello的对象,后面的使用就和在本地使用一样了。
虽说执行远程方法的时候代码是在远程服务器上执行的,但实际上我们还是需要知道有哪些方法,这时候接口的重要性就体现了,这也是为什么我们前面要继承Remote
并将我们需要调用的方法写在接口IRemoteHelloWorld
里,因为客户端也需要用到这个接口。
为了理解RMI的通信过程,我们用wireshark抓包看看:
第一次建立TCP连接是连接远端192.168.78.128
的1099端口,这也是我们在代码里看到的端口,二者进行沟通后,我们向
https://blog.csdn.net/xinghun_4/article/details/45787549
https://www.codetd.com/article/10038981