RPC和RMI
RPC(Remote Procedure Call Protocol) 即远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
RMI:远程方法调用(Remote Method Invocation)是一种面向对象的RPC,是RPC在java层面上的一种实现。
前期准备
1.RpcDefinition
当客户端需要进行远程方法调用时,服务器端需要知道该方法存在且可以执行。所以需要一个RpcFactory类来存放这些服务,并且提供注册方法。因为要根据客户端发送的消息来决定方法的执行,所以需要一个PrcDefinition类将类、对象、封装起来。
public class RpcDefinition {
private Class<?> klass;
private Object object;
private Method method;
}
RpcDefinition() {
}
RpcDefinition(Class<?> klass, Object object, Method method) {
this.klass = klass;
this.object = object;
this.method = method;
}
2.RpcFactory
我们将RpcDefinition存在Map中,将它们唯一的id作为键。并且提供注册方法,服务器提供给客户端的服务接口和该接口的实现类是必要的,在此提供三种传参方式注册以方便用户使用。其中Map只需要一个!
首先先判断传进来的interfaces是否为接口,以及klass是否为接口的实现类,否则均抛出异常。
接口的实现类只需要实例化一次即可!
接下来遍历接口中的方法,并生成每个方法唯一的id,并作为Map中的键。为了保证id的绝对唯一性,并且考虑到不同接口中的同名方法,这里的id等于(interfaces.getName() + interfaceMethod.toString()).hashCode()。
最后提供一个根据id查找的方法。
public class RpcFactory {
private static final Map<Integer, RpcDefinition> rmiMap;
static {
rmiMap = new HashMap<>();
}
public RpcFactory() {
}
private RpcFactory registry(Class<?> klass, Class<?> interfaces, Object object) throws Exception {
if (!interfaces.isInterface()) {
throw new Exception("[" + interfaces.getName() + "]不是接口!");
}
if (!interfaces.isAssignableFrom(klass)) {
throw new Exception("[" + klass.getName() + "]不是接口[" + interfaces.getName() + "]的实现类");
}
Object obj = object == null ? klass.newInstance() : object;
Method[] interfaceMethods = interfaces.getDeclaredMethods();
for (Method interfaceMethod : interfaceMethods) {
String methodName = interfaceMethod.getName();
Class<?>[] paraTypes = interfaceMethod.getParameterTypes();
int id = (interfaces.getName() + interfaceMethod.toString()).hashCode();
Method method = klass.getMethod(methodName, paraTypes);
RpcDefinition rmiDefinition = new RpcDefinition();
rmiDefinition.setKlass(klass);
rmiDefinition.setObject(obj);
rmiDefinition.setMethod(method);
rmiMap.put(id, rmiDefinition);
}
return this;
}
RpcDefinition getRmiDefinition(int id) {
RpcDefinition rmi = rmiMap.get(id);
return rmi;
}
}
其它两种传参方式:
因为上述方法的实现足够完善,下面两种方式只需调整参数并重新调用上述方法即可,极大地简化了代码编写。
public RpcFactory registry(Class<?> klass, Class<?> interfaces) throws Exception {
return registry(klass, interfaces, null);
}
public RpcFactory registry(Object object, Class<?> interfaces) throws Exception {
return registry(object.getClass(), interfaces, object);
}
3.RpcInvoker
该类为服务器端调用,真正的方法执行是在这里进行!开启线程侦听客户端,解析客户端发来的json字符串,根据id在RpcFactory中找到对应方法并执行,将结果发送回客户端。
public class RpcInvoker implements Runnable {
private static final Type type = new TypeToken<Map<String, String>>() {}.getType();
private Socket socket;
private DataInputStream dis;
private DataOutputStream dos;
private Gson gson;
RpcInvoker(Socket socket, Gson gson) {
this.gson = gson;
this.socket = socket;
try {
this.dis = new DataInputStream(socket.getInputStream());
this.dos = new DataOutputStream(socket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
// json字符串解析
// 将参数解析成数组paras
private Object[] getParas(String paraString, Class<?>[] paraType) {
Map<String, String> paraStringMap = gson.fromJson(paraString, type);
int paraCount = paraStringMap.size();
if (paraCount <= 0) {
return new Object[] {};
}
Object[] paras = new Object[paraCount];
for (int index = 0; index < paraStringMap.size(); index++) {
String key = "arg" + index;
String value = paraStringMap.get(key);
paras[index] = gson.fromJson(value, paraType[index]);
}
return paras;
}
@Override
public void run() {
try {
int methodId = dis.readInt();
String paraString = dis.readUTF();
System.out.println("接收到的code" + methodId);
RpcDefinition rmiDefinition = new RpcFactory().getRmiDefinition(methodId);
if (rmiDefinition == null) {
throw new Exception(methodId + "没有找到与之匹配的方法!");
}
Object object = rmiDefinition.getObject();
Method method = rmiDefinition.getMethod();
Object[] paras = getParas(paraString, method.getParameterTypes());
Object result = method.invoke(object, paras);
dos.writeUTF(gson.toJson(result));
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务器端
RpcServer类:
这个类是对外提供的一个类,实现了Runnable接口,作为服务器,需要有线程来时刻侦听客户端的连接请求,并且添加线程池来控制客户端连接。侦听到线程只需threadPool.execute(new RpcInvoker(client, gson));可见RpcInvoker类的必要性,高内聚。
采用默认端口号54189,也可添加Properties配置文件配置。
public class RpcServer implements Runnable {
private int port;
private ServerSocket serverSocket;
private volatile boolean goon;
private ThreadPoolExecutor threadPool;
public static final int DEFAULT_PORT = 54189;
private static final Gson gson = new GsonBuilder().create();
public RpcServer() {
this.threadPool = new ThreadPoolExecutor(30, 50, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<>(50));
this.port = DEFAULT_PORT;
}
public void initServer(String path) {
String portString = PropertiesParser.value("RMI.port");
if (portString == null) {
try {
PropertiesParser.loadProperties(path);
} catch (Exception e) {
e.printStackTrace();
}
}
portString = PropertiesParser.value("RMI.port");
if (portString != null) {
port = Integer.valueOf(portString);
}
}
public void setPort(int port) {
this.port = port;
}
public void startup() {
try {
serverSocket = new ServerSocket(port);
goon = true;
new Thread(this, "RMI-Server").start();
} catch (IOException e) {
e.printStackTrace();
}
}
public void shutdown() {
goon = false;
if (!threadPool.isShutdown()) {
threadPool.shutdown();
}
if (serverSocket != null) {
try {
if (!serverSocket.isClosed()) {
serverSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
serverSocket = null;
}
}
}
@Override
public void run() {
while (goon) {
try {
Socket client = serverSocket.accept();
threadPool.execute(new RpcInvoker(client, gson));
} catch (IOException e) {
if (goon != false) {
goon = false;
e.printStackTrace();
}
}
}
}
}
客户端
客户端在设置了IP和port后,只需将接口、需要调用的方法、参数发送给服务器即可。最后通过getProxy()方法即可得到执行结果。
public class RpcClient {
private static final Gson gson = new GsonBuilder().create();
private String rmiIp;
private int rmiPort;
public RpcClient() {
}
public void setIp(String rmiIp) {
this.rmiIp = rmiIp;
}
public void setPort(int port) {
this.rmiPort = port;
}
private String parasToGson(Object[] args) {
ArgumentMaker am = new ArgumentMaker();
if (args != null) {
for (Object arg : args) {
am.addArg(arg);
}
}
return am.toString();
}
private Object invoker(Class<?> interfaces, Method method, Object[] args) {
Object result = null;
try {
Socket socket = new Socket(rmiIp, rmiPort);
DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
String id = interfaces.getName() + method.toString();
dos.writeInt(id.hashCode());
dos.writeUTF(parasToGson(args));
String resString = dis.readUTF();
result = gson.fromJson(resString, method.getReturnType());
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return result ;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<?> interfaces) {
return (T) Proxy.newProxyInstance(interfaces.getClassLoader(),
new Class<?>[] { interfaces }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return invoker(interfaces, method, args);
}
});
}
}
测试
我们只需要写出服务接口,并完成其实现类,通过new RpcServer()和new RpcFactory();并在factory中注册接口和实现类即可!(registry)
示例:
public static void main(String[] args) {
RpcServer rmis = new RpcServer();
RpcFactory rmiFactory = new RpcFactory();
try {
rmiFactory.registry(RMIExample.class, IRMIExample.class);
} catch (Exception e) {
e.printStackTrace();
}
rmis.setPort(54189);
rmis.startup();
Complex c = new Complex(1.2, 3.4);
RpcClient client = new RpcClient();
client.setIp("127.0.0.1");
client.setPort(54189);
IRMIExample irm = client.getProxy(IRMIExample.class);
irm.fun1("WY");
irm.fun3(12, "abc", c);
rmis.shutdown();
}
输出结果:
优化及改进
后续可将IP和port封装成一个Node类,以及添加之前做过的计时器工具来检测连接超时等等,并且为Dubbo底层的模拟做好准备工作。
可结合AOP以及依赖注入。
在实际应用过程中,客户端除了可做为客户端之外,也可以作为服务器,那么就需要一个注册中心来管理这些服务器,去给子服务器和客户端分配IP和port,以达到后面将要实现的“云”的目的。