JavaRMI实现rpc调用
本来想着用c++做rpc调用的,那个需要用到grpc库,这个库挺厉害的,基本上就是各个语言之间都可以相互调用,我们之前做这种调用都是使用socket,这样实在太蠢了。在grpc里面,各个接口统一由一种protobuf来做序列化,并且由这个序列化后的文件比原本的文件空间上更加高效。
一般来说,远程过程调用需要一个注册中心,我们实现的方法应该都能在这个注册中心找到,这就需要服务端和客户端都能够发现这个注册中心。大部分rpc大致可分为5个部分。User User-stub PRCRuntime Server-stub Server,其中RPCRuntime大致可以认为是我们需要的注册中心,在user调用函数后,数据在user-stub打包发送给rpcruntime,rpcruntime通过网络协议将请求的数据发送给server-stub,server-stub将数据解包后传给server端,server端根据数据调用相应的函数并将结果返回。(返回的方法是上面过程的逆过程)
因为是远程过程调用,这儿就需要我们使用序列化,序列化的作用是以二进制的方式传输数据,包括传输类,int等等。
下面我们专门介绍下java的rpc,就是java自己实现的一个远程过程调用,唤做rmi,这个东西呢,基本上就是java的自娱自乐,只能java程序之间相互调用,不能跨语言相互调用,这个东西调用的方式是通过interface定义函数,然后服务端实现这个interface,并将实现好的类暴露给注册中心。
调用的时候,先运行注册中心。
在MyServer同一个文件夹下输入 rmiregistry 1099,开启注册中心,为啥要在这个文件夹下运行这个程序,我其实是不太清楚的,但是好像是需要加载相关的类,server实现的时候会生成,具体啥情况我也不太清楚,反正这么运行就对了。
然后运行java MyServer,最后运行java MyClient,直接上代码吧。
MyServer.java:
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class MyServer {
public static void main(String[] args) throws Exception {
try {
String name = "Library";
MyLibrary library = new MyLibraryImpl();
MyLibrary skeleton = (MyLibrary) UnicastRemoteObject.exportObject(library, 0);
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
System.out.println("Registering Library Object");
registry.bind(name, skeleton);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
说实话,这个代码结构我是没咋搞懂的,但是能跑,至于为啥要这么写,我不咋明白。这儿的MyLibrary是我们定义的interface类,我估摸着好像是首先要根据我们实现了接口方法的类实例化一个接口,然后将这个接口转化为能够远程调用的实例化对象,最后开启注册中心并且根据定义名称完成绑定。
MyClient.java:
import java.io.*;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Scanner;
public class MyClient {
public static void main(String[] args) throws Exception {
String name = "Library";
String ServerIP = "127.0.0.1";
int ServerPort = 1099;
Registry registry = LocateRegistry.getRegistry(ServerIP, ServerPort);
MyLibrary library = (MyLibrary) registry.lookup(name);
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String choice;
String name_;
int ID;
BookList tmp;
while (true) {
System.out.println("Please input your choice:");
choice = reader.readLine();
switch (choice) {
case "add":
System.out.println("Please input name:");
name_ = reader.readLine();
System.out.println("Please input ID:");
ID = new Scanner(System.in).nextInt();
if (library.add(new Book(name_, ID)))
System.out.println("Add succeed");
else
System.out.println("Add fail");
break;
case "queryByID":
System.out.println("Please input ID:");
ID = new Scanner(System.in).nextInt();
System.out.println("Book name: " + library.queryByID(ID).name);
break;
case "queryByName":
System.out.println("Please input name:");
name_ = reader.readLine();
tmp = library.queryByName(name_);
for (Book book : tmp.a) {
System.out.println("Book ID:" + book.ID);
}
break;
case "delete":
System.out.println("Please input book ID:");
ID = new Scanner(System.in).nextInt();
if (library.delete(ID))
System.out.println("Delete succeed");
else
System.out.println("Delete fail");
break;
default:
System.out.println("Choice wrong, Please reinput:");
break;
}
}
}
}
这儿我不严格推理估摸着首先是获取注册中心相关方法,然后实例化一个对象等于返回的对象实例,但是由于我们是个接口,方法实现的具体方法是未知的,实际上是每次调用接口的方法最后是完成的rpc调用。
MyLibrary.java:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface MyLibrary extends Remote {
Boolean add(Book b) throws RemoteException;
Book queryByID(int bookID) throws RemoteException;
BookList queryByName(String name) throws RemoteException;
Boolean delete(int bookID) throws RemoteException;
}
这就是我们接口类的定义,注意,我们这个接口继承了Remote,并且每个接口方法还要注意抛出问题。
MyLibraryImpl.java:
import java.rmi.RemoteException;
import java.util.Vector;
public class MyLibraryImpl implements MyLibrary {
public Vector<Book> library;
MyLibraryImpl() {
library = new Vector<Book>();
}
@Override
public Boolean add(Book b) throws RemoteException {
System.out.println("Add method called");
try {
library.add(b);
return true;
} catch (Exception e) {
System.out.println(e.getMessage());
return false;
}
}
@Override
public Book queryByID(int bookID) throws RemoteException {
System.out.println("queryByID method called");
Book tmp = new Book();
try {
for (Book book : library) {
if (book.ID == bookID)
tmp = book;
}
return tmp;
} catch (Exception e) {
System.out.println(e.getMessage());
return tmp;
}
}
@Override
public BookList queryByName(String name) throws RemoteException {
System.out.println("queryByName method called");
BookList List = new BookList();
try {
for (Book book : library) {
if (book.name.equals(name))
List.a.add(book);
}
return List;
} catch (Exception e) {
System.out.println(e.getMessage());
return List;
}
}
@Override
public Boolean delete(int bookID) throws RemoteException {
System.out.println("delete method called");
try {
for (Book book : library) {
if (book.ID == bookID)
library.remove(book);
return true;
}
return false;
} catch (Exception e) {
return false;
}
}
}
这个是方法实现的具体步骤,运行在服务端。
Book.java:
这个就是我们每本书的定义,注意这个类需要继承Serializable类,要不然最后类无法序列化,也就是说在服务端完成相关运算后无法将这个类发送回来。但是为啥还需要继承Cloneable呢,这个我也不是很清楚,但是这么写了没出错。还有个那个final的long是好像同步用的,不太清楚。
BookList.java
import java.util.Vector;
import java.io.Serializable;
public class BookList implements Serializable, Cloneable {
private static final long serialVersionUID = 42L;
public Vector<Book> a;
BookList() {
a = new Vector<Book>();
}
}
RPC入门总结(一)RPC定义和原理
RPC入门总结(二)RMI的原理和使用
RPC入门总结(三)RMI+Zookeeper实现远程调用框架