Java RMI & GSON
起因是分布式与云计算课程需要做这样一个项目… 虽然 RMI 已经是十多年前的东西了,不过课程需要,还是得硬上。第一次作业是基于RMI写的,本次作业则是自己重写一个RMI。助教提供的代码中已经完成了 registry 部分,因此本文章的内容主要注重于 Communication Module 部分。
RMI 架构&通信流程
先说说 RMI 的架构和通信流程,便于接下来的理解。
RMI 架构
RMI 大致上可以分为三个部分:registry, server, client
具体上:
- registry 负责存储服务的信息,并提供 server 注册服务和 client 发现服务的相关方法
- server 是具体的服务器业务类,开启的时候要向 registry 注册自己提供的服务,并维护 rorTable (RemoteObjectReferenceTable),以便 client 在进行远程过程调用的时候能找到对应的远程对象。
- clinet 是具体的客户端业务类,开启后先向 registry 查询服务,得到服务端留下的 ROR (RemoteObjectReference),然后就可以用其中储存的 IP, 端口等信息直接和服务器交流了(之后就没有 registry 什么事情了…)
需要注意的两个表/mapping:
- registry 上:Registry: ServiceName[String] -> ror[RemoteObjectReference]
- server 上:ror[RemoteObjectReference] -> realObject[Object]
一个可以参考的架构图(随手画的,还请见谅):
RMI 通信流程
大概可以看这个说明:
1 s, create rorTable
2 s, create object
3 s, generate ror with object
3.1 s, put object in self.rorTable
3.2 s, start server-side cm listening
4 s->r, register in registry, servicename -> ror
5 c->r, using servicename to fetch ror
6 c, get stub from ror.localize()
7 c, stub being used, remote method invoked
8 c, invocation caught in RORIn…Handler
9 c, read ip:port in ror
10 c->s, init TCP conn with ip:port
11 c->s, send ror, method, args
12 s, read ror, method args
13 s, using ror find object from self.rorTable
14 s, do the real invocation
15 s->c, send back result
16 c, show result. rmi finished.
基于 GSON 的 JSON 化传递
坑点:
-
Class<?>[] 不支持直接json化,需要特殊处理(实际上就是把method的参数类型和参数本身都转换成string,然后再在远端恢复),具体操作如下:
远程消息传递类(RemoteInvokeInfo):public String methodName; public String[] argClassNames; public Object[] args;
服务端通信模块 (ServerCM):
Class[] argClasses = new Class[rii.argClassNames.length]; for (int i = 0; i < argClasses.length; i++) { argClasses[i] = RemoteInvokeInfo.parseType(rii.argClassNames[i]); System.out.println(args[i].toString()); args[i] = gson.fromJson(args[i].toString(),argClasses[i]); } method = instance.getClass().getMethod(rii.methodName,argClasses); result = method.invoke(instance,args);
类型转换特殊处理 (RemoteInvokeInfo.parseType):
// convert from className str to class // https://stackoverflow.com/questions/5032898/how-to-instantiate-class-class-for-a-primitive-type public static Class<?> parseType(final String className) { switch (className) { case "boolean": return boolean.class; case "byte": return byte.class; case "short": return short.class; case "int": return int.class; case "long": return long.class; case "float": return float.class; case "double": return double.class; case "char": return char.class; case "void": return void.class; default: try { return Class.forName(className); } catch (ClassNotFoundException ex) { throw new IllegalArgumentException("Class not found: " + className); } } }
-
如果要无痛用gson,最好设置好完备的getter/setter,否则会遇到一些奇怪的错误,以
java.lang.UnsupportedOperationException: Attempted to serialize java.lang.Class :***. Forgot to register a type adapter?
为主。如果你是 IDEA 用户,直接
Alt+Insert
即可。
还没有解决的问题
-
这里实际上还是单例模式,client 每次只要向 registry 请求同一个 service,拿回来的总是同一个 ror,对应的在 server 上也总是同一个对象,并没有实现每一个 client 都能拿到 service 上的一个新对象。
可能的解法大概有以下三种,然而我都没有尝试过:
- 在 registry 上做手脚:允许多维护一层,可以多个 server 用同一个 ServiceName 注册服务,然后在 client 查询的时候随机分配
- 在 server 上做手脚:把 rorTable 从一对一改成一对多,每次根据不同的客户端调用取不同的对象
- 在 client 和 server 上做手脚:server 在注册服务的时候提供一个 newInstance 方法和对应的 deleteInstance 方法,client 开启的时候新建一个新的对象,结束的时候销毁。
就这样吧。祝各位编程愉快。