RPC和RMI
RPC的全称为Remote Procedure Call,即远程过程调用。
RMI的全称为Remote Method Invocation,即原程方法调用。
RPC客户器和RPC服务器可以运行在不同的java虚拟机中。RPC客户端只需要引入一个接口,而这个接口的实现以及调用时所需要的数据都在RPC服务器中。在RPC服务器在客户端调用之前就将客户端可能用到的所有接口regist(注册),RPC客户端在“执行”相关方法时,其实际上只是发送相关方法的参数和序列号到RPC服务器端,RPC服务器在调用相关方法之后将调用的结果回送到RPC客户端。也可以称RPC客户端为伪执行端,而RPC服务器为真正执行端。RPC主要依赖的技术是序列化、反序列化、传输协议。RPC有多种框架,有RMI、Hessian、Dubbo框架等。
RMI的实现过程为:
- 服务器将客户端将要可能调用的所有接口注册,并保存。
- 客户端使用动态代理代理我们将要在服务器端调用的接口,使客户端调用服务器端相关方法就像调用本机方法一样。
- 客户端将本地将要调用的接口方法的参数及序列号通过网络发送到服务器。
- 服务器接收到客户端发送的信息后,从保存这些方法中进行查找,通过反射调用该方法,并进行一定的拦截。
- 服务器完成拦截后将执行的结果回送给客户端。
- 客户端接收到执行结果。
下图为实现的大概框图(注:上面的1、2、3序列号与下图中的序列号相互不对应)
上面说了那么多(有点啰嗦,还可能表达不清楚),还是不如具体看代码来的实在。
首先是建立服务器,其框架如图
创建一个RPCServer类
类中成员:
private ServerSocket server;
private int rpcServerPort;//服务器端口号
private boolean goon;//判断服务器是否继续侦听
private long count;//客户端连接个数
private RpcBeanFactory rpcBeanFactory;//注册相关方法时用到
类中方法:
public class RpcServer implements Runnable {
public RpcServer(int rpcServerPort) {
goon = false;
this.rpcServerPort = rpcServerPort;
this.rpcBeanFactory = new RpcBeanFactory();
}
/*
* 扫描包
*/
public void scanPackage(String packageName) {
new PackageScanner() {//自己的工具
@Override
public void dealClass(Class<?> klass) {
if (klass.isAnnotationPresent(RpcInterface.class)) {//扫描到带有@RpcInterface的注解的类
Class<?>[] interfaces = klass.getInterfaces();//获取该类所有实现了的接口
for (Class<?> inter : interfaces) {
if (inter.equals(Serializable.class)) {//若接口为Serializable,则不注册
continue;
}
RpcBeanRegistry.registInterface(inter, klass, rpcBeanFactory);//注册相关接口
}
}
}
}.packageScan(packageName);;
}
RpcBeanDefination getRpcBeanDefination(String rpcId) {
return rpcBeanFactory.getRpcBeanDefination(rpcId);
}
/*
* 创建服务器,启动线程
*/
public void startRpc() {
try {
goon = true;
server = new ServerSocket(rpcServerPort);
new Thread(this, "RpcServer").start();
} catch (IOException e) {
e.printStackTrace();
stop();
}
}
/*
*关闭服务器
*/
private void stop() {
if (server != null || !server.isClosed()) {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
server = null;
}
}
}
/*
* 线程
*/
@Override
public void run() {
while (goon) {
try {
Socket socket = server.accept();
new RpcServerExcutor(socket, ++count, this);//进行消息的传递
} catch (IOException e) {
e.printStackTrace();
goon = false; //若侦听发生异常,不在继续侦听
}
}
stop(); //在执行完后要关闭服务器
}
}
在这个类中线程启动后,在本类中不进行消息的传送,而是在下一个类RpcServerExcutor中完成消息的交互。因为客户端发送数据不是一次发送完,而是将序列号和方法参数分开发送。使用上述做法是为了让服务器侦听一次客户端的请求,而客户端可以发送多条消息,也就是为了实现服务器和客户端之间的短连接。(之所以要去掉Serializable接口,是因为客户端传递的方法序列号必须是在该类已经实现了这个接口后才有的,但是在注册时不需要将该接口注册(实现了接口的类必须要实现Serializable接口,否则因没有序列号而产生异常))
RpcServerExcutor类
成员:
private Socket socket;//侦听到的客户端
private ObjectInputStream ois;//输入流
private ObjectOutputStream oos;//输出流
private RpcServer rpcServer;//与RpcServer类之间进行联系
方法:
public class RpcServerExcutor implements Runnable {
//构造方法
RpcServerExcutor(Socket socket, long count, RpcServer rpcServer) throws IOException {
this.socket = socket;
this.rpcServer = rpcServer;
this.ois = new ObjectInputStream(this.socket.getInputStream());
this.oos = new ObjectOutputStream(this.socket.getOutputStream());
new Thread(this, "count").start();//启动线程
}
/*
*断开与客户端的连接
*/
void closeRpc() {
try {
if (ois != null) {
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
ois = null;
}
try {
if (oos != null) {
oos.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
oos = null;
}
try {
if (socket != null || !socket.isClosed()) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
socket = null;
}
}
//线程
@Override
public void run() {
//获取客户端发送过来的方法,对象,参数;根据这些数据确定方法;获取方法的返回值;将返回值发送给客户
try {
String rpcId = ois.readUTF();//接收到序列号
Object[] parameters = (Object[])ois.readObject();//就收所有参数并构成数组
RpcBeanDefination rpcBeanDefination = rpcServer.getRpcBeanDefination(rpcId);//根据序列号找到相关方法
Object object = rpcBeanDefination.getObject();//得到要执行方法的对象
Method method = rpcBeanDefination.getMethod();//得到方法
Object result = method.invoke(object, parameters);//通过对象和参数进行反射
oos.writeObject(result);//发送执行结果
} catch (Exception e) {
e.printStackTrace();
closeRpc();//发生异常则关闭与该客户端的连接
}
}
}
本类的作用就是将客户端发送的数据接收并发送执行后的结果,也就是真正与客户端信息交互的地方
在RpcServer类中提到了RpcBeanFactory类和RpcBeanRegistry类
RpcBeanFactory类:
public class RpcBeanFactory {
//其键为方法的序列号,值为RpcBeanDefination类(在后面讲到)
private final Map<String, RpcBeanDefination> rpcBeanMap;
RpcBeanFactory() {
this.rpcBeanMap = new HashMap<>();
}
/*
*给rpcBeanMap中添加元素
*/
void addRpcBeanMap(String rpcId, RpcBeanDefination rpcBeanDefination) {
RpcBeanDefination rbd = rpcBeanMap.get(rpcId);
if (rbd != null) {//若该序列号的所有方法已经添加
return;
}
this.rpcBeanMap.put(rpcId, rpcBeanDefination);//真正的添加
}
/*
*通过方法序列号获得RpcBeanDefination类中的所有成员
*/
RpcBeanDefination getRpcBeanDefination(String rpcId) {
return rpcBeanMap.get(rpcId);
}
}
在该类中没有真正的添加,只是产生了添加的方法
RpcBeanRegistry类:
public class RpcBeanRegistry {
/*
*通过传递过来的接口类型、实现接口的类、rpcBeanFactory进行注册
*/
static void registInterface(Class<?> interfaces, Class<?> klass,
RpcBeanFactory rpcBeanFactory) {
if (!interfaces.isAssignableFrom(klass)) {//若传递来的klass没有实现interfaces接口
return;
}
try {
doRegist(interfaces, klass.newInstance(), rpcBeanFactory);//添加(本类中的方法)
} catch (Exception e) {
e.printStackTrace();
}
}
private static void doRegist(Class<?> interfaces, Object object, RpcBeanFactory rpcBeanFactory) {
Method[] methods = interfaces.getDeclaredMethods();//得到接口中的所有方法
for (Method method : methods) {
RpcBeanDefination rbd = new RpcBeanDefination();
rbd.setKlass(interfaces);
rbd.setMethod(method);
rbd.setObject(object);
String rpcId = String.valueOf(method.toString().hashCode());//产生方法的序列号
rpcBeanFactory.addRpcBeanMap(rpcId, rbd);//将接口中的每一个方法都注册(保存到rpcBeanMap中)
}
}
}
在这个类中将扫描的接口、对象、方法进行添加(在这里添加的序列号是通过method.toString().hashCode()形成的,这样可以保证序列的唯一性。如果只有method.hashCode()即只要是方法名称和参数类型和个数一样他们的序列号就一样。如果是两个不同类中含有相同的方法就会出现这种情况)
RpcBeandDefination类:
private Class<?> klass;
private Method method;
private Object object;
在这个类中只有接口、方法、实现接口的对象三个成员以及他们的set()和get()方法
客户端框架如图
在客户端有两个类
我们先来看RpcClentExcutor类
类中成员:
private Socket socket;
private String rpcClientIp;//客户端的IP
private int rpcServerPort;//服务器的port
类中方法:
public class RpcClientExcutor {
//从用户得到IP、port
RpcClientExcutor(String rpcClientIp, int rpcServerPort){
this.rpcClientIp = rpcClientIp;
this.rpcServerPort = rpcServerPort;
}
/*
*与服务器进行交互
*/
Object rpcExcutor(String rpcId, Object[] paras) throws Exception {
socket = new Socket(rpcClientIp, rpcServerPort); //连接服务器
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());//输出流
oos.writeUTF(rpcId);//发送方法的序列号
oos.writeObject(paras);//发送方法参数
//输入流(注:下面这一行代码必须要写在发送信号之后,否则会发生阻塞)
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Object result = ois.readObject();//读取服务器发送回来的数据
closeRpc(ois, oos, socket);//断开与服务器的连接
return result;
}
/*
*关闭客户端
*/
void closeRpc(ObjectInputStream ois, ObjectOutputStream oos, Socket socket) {
try {
if (ois != null) {
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
ois = null;
}
try {
if (oos != null) {
oos.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
oos = null;
}
try {
if (socket != null || !socket.isClosed()) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
socket = null;
}
}
}
在这个类中主要进行的方法是将RpcClient产生代理后的方法的参数和序列号进行发送
RpcClient类:
public class RpcClient {
//与RpcClient进行连接
private RpcClientExcutor rpcClientExcutor;
//与客户传送的IP、port连接
public RpcClient(String rpcClientIp, int rpcServerPort) {
rpcClientExcutor = new RpcClientExcutor(rpcClientIp, rpcServerPort);
}
/*
*产生代理
*/
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<?> klass) {
return (T)Proxy.newProxyInstance(klass.getClassLoader(), new Class<?>[]{klass},
new InvocationHandler() {
@Override
public Object invoke(Object arg0, Method method, Object[] args) throws Throwable {
String rpcId = String.valueOf(method.toString().hashCode());//产生方法序列号
Object result = rpcClientExcutor.rpcExcutor(rpcId, args);//调用RpcClientExcutor类中的rpcExcutor()方法
return result;//返回执行结果
}
});
}
}
前面写了那么多,最后还是要看结果,下面我们来测试一下
首先建立一个接口
public interface ITestForRPC {
UserModel getUserById(String id);
}
然后建立一个实现该接口的类,并且带有@RpcInterface注解(一定要实现Serializable接口,否则会因为产生不了端口号而出现异常)
@RpcInterface
public class UserAction implements ITestForRPC, Serializable {
private static final long serialVersionUID = -7874287356974650711L;
@Override
public UserModel getUserById(String id) {
UserModel user = new UserModel();
user.setId(id);
user.setName("张三");
user.setSex(true);
return user;
}
}
UserModel是一个类,其中有name,id,sex成员
然后启动服务器:
public class TestServer {
public static void main(String[] args) {
RpcServer rpcServer = new RpcServer(11520);//设置端口号
rpcServer.scanPackage("com.satellite.rpc.cor");//自己的包路径
rpcServer.startRpc();
}
}
最后启动客户端:
public class TestClient {
public static void main(String[] args) {
RpcClient rpcClient = new RpcClient("192.168.1.206", 11520);//自己电脑的Ip和服务器的端口号
ITestForRPC proxy = rpcClient.getProxy(ITestForRPC.class);//产生代理
UserModel userModel = proxy.getUserById("03161101");
System.out.println(userModel);//输出userModel
}
}
执行结果:
这只是简单的实现,还有很多需要改进的地方,比如将端口号和ip进行文件配置;添加拦截器等。