每篇一句:
We laughed and kept saying ‘see u soon’, but inside we both knew we’d never see each other again.
RPC简介:
RPC(Remote Procedure Call)—-远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如 TCP 或 UDP ,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
RPC采用 客户机/服务器 模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
Java实现:
Server提供:
HelloWorldService.java
public interface HelloWorldService { public String hello(String name); }
HelloWorldServiceImpl.java
public class HelloWorldServiceImpl implements HelloWorldService { @Override public String hello(String name) { return "Hello! " + name; } }
- RPC_Server.java
import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Method; import java.net.ServerSocket; import java.net.Socket; import java.util.Arrays; public class RPC_Server { public static void main(String[] args){ ServerSocket ss = null; Socket socket = null; ObjectOutputStream out = null; try { ss = new ServerSocket(8090); while(true){ System.out.println("----Server Start!-----"); socket = ss.accept(); // 从socket获取信息 ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); // 获取消息的顺序要与客户端添加的顺序一致 String interface_name = input.readUTF(); // 请求调用的接口名称 System.out.println("调用的服务为:" + interface_name); String method_name = input.readUTF(); // 请求调用的方法名称 System.out.println("调用的方法为:" + method_name); Class<?>[] parameterType = (Class<?>[]) input.readObject(); // 被调用方法的参数类型列表 Object[] arguments = (Object[]) input.readObject(); // 客户端传过来的参数列表 System.out.println("参数列表为 : " + Arrays.toString(arguments)); // 根据得到的被调用接口名称得到Server的Class Class<?> service_Interface_Class = Class.forName(interface_name); // 根据方法名称和参数类型得到Server的method Method method = service_Interface_Class.getMethod(method_name, parameterType); // 服务实例化 Object service = service_Interface_Class.newInstance(); // 反射执行这个方法 Object result = method.invoke(service, arguments); // 写回处理结果 out = new ObjectOutputStream(socket.getOutputStream()); out.writeObject(result); } } catch (Exception e) { e.printStackTrace(); }finally { try { out.close(); } catch (IOException e) { e.printStackTrace(); } if (ss != null){ try { ss.close(); } catch (IOException e) { e.printStackTrace(); } ss = null; } } } }
Client调用:
- RPC_Client.java
import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; public class RPC_Client { public void hello(String name){ ObjectOutputStream out = null; Socket socket = null; try { // 即将调用的接口名 String interFaceName = "HelloWorldServiceImpl"; // 即将调用的方法名 String methodName = "hello"; // 参数类型列表 Class<?>[] arguments_types = {java.lang.String.class}; // 参数 Object[] arguments = {name}; socket = new Socket("127.0.0.1", 8090); // 将调用的信息写入out out = new ObjectOutputStream(socket.getOutputStream()); out.writeUTF(interFaceName); out.writeUTF(methodName); out.writeObject(arguments_types); out.writeObject(arguments); // 获取结果 ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); Object result = in.readObject(); System.out.println(result.toString()); } catch (Exception e) { e.printStackTrace(); }finally { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args){ RPC_Client c = new RPC_Client(); c.hello("RPC"); } }
- RPC_Client.java
- 测试结果:
- Server端:
- Client端:
- Server端:
过程分析:
在这个简单的Demo中,Client端远程调用Server端的hello() 方法,其内部操作大致如下:
启动RPC_Server,服务器端开始监听本机的
8090
端口;启动RPC_Client,客户端将即将调用方法的相关信息(类名、方法名、参数类型列表、参数列表)写入流,通过新建的socket通过
8090
端口传到服务器;服务器端检测到有客户端调用本机提供的服务,从socket获取信息;
根据获取到的信息,调用本机对应的服务,执行远程过程;
服务器端将执行结果写入流,通过socket传回客户端;
客户端接受服务器端返回的执行结果。
总结:
本文只是RPC功能的简单实现,效率较慢,还存在着很多可以改进的方面:
比如:
通信机制:本文采用的通信方式socket是基于BIO实现的,I/O效率不高,可以使用NIO或直接使用Netty代替BIO实现;
数据传输:本文数据传输速率较慢,可以采用基于JSON数据传输的方式;
- 序列化:在服务器端与客户端进行通信时,需要将对象名称、方法名称、参数等信息转换成二进制传输,本文采用的是Java原生的readUTF(),writeUTF()等序列化机制,占内存太多,运行效率也不高,可以使用其他开源的序列化机制,如:Hadoop Avro与Google protobuf等;
另外:
- 本次实现只是一个客户端调用服务端的服务,如果有多个客户端同时调用,就要考虑多线程、多进程的方式,同时如何区分不同客户端,如何将执行结果返回正确的对应客户端,也是需要考虑的问题了。
可见:要实现完善的RPC功能或者实现一个RPC框架需要考虑的问题很多,希望这篇文章对读者有所启发,大家有什么看法欢迎在下方评论。
参考: