第一节 RMI(Remote Mothed invoke)是什么?
一句话简单总结,我个人认为RMI就是RPC的java版本实现,当然他们本身底层也有很大的区别,比如序列化协议,网络通讯模型等等,今天我们就来简单的学习一下NC使用的RMI技术;
第二节 RMI简单实现
废话不多说我们直接上代码,我这里区分了服务端与客户端,见下图:
HelloService接口存根
1
2
3
4
5package RPC;
public interface HelloService {
public String hello(String name);
}上述接口的实现类HelloServiceImpl
1
2
3
4
5
6
7
8
9
10package RPC;
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String name) {
return name+":from hkserver!";
}
}服务器启动接口类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package RPC;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public interface Server {
//启动服务器
public void start() ;
//停止服务器
public void stop();
//往服务容器中注册服务
public void register() throws ClassNotFoundException;
}服务器实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112package RPC;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.TypeVariable;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;
public class ServerImpl implements Server {
//注册中心用于处理
private Map<String,Class> serviceRegister = new HashMap<String,Class>();
private static final int PORT = 9999;
//开放端口提供给client进行调用
@Override
public void start() {
ServerSocket ss = null;
OutputStream os = null;
InputStream is = null;
Socket sk = null;
try {
ss = new ServerSocket();
ss.bind(new InetSocketAddress(PORT));
sk = ss.accept();
//阻塞等待
is = sk.getInputStream();
ObjectInputStream ois = new ObjectInputStream(is);
String servicename = ois.readUTF();
String methodname = ois.readUTF();
Class[] paraTypes = (Class[])ois.readObject();
Object[] args= (Object[])ois.readObject();
Class serviceClass = serviceRegister.get(servicename);
@SuppressWarnings("unchecked")
Method invokeMethod = serviceClass.getMethod(methodname, paraTypes);
Object result = invokeMethod.invoke(serviceClass.newInstance(), args);
os = sk.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(result);
oos.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
ss.close();
sk.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
@Override
public void stop() {
}
@Override
public void register() throws ClassNotFoundException {
serviceRegister.put("HelloService", Class.forName("RPC.HelloServiceImpl"));
}
}
5.服务端main方法
1 | package RPC; |
以上我们服务端代码就完成了,简单而言就是通过一个main方法来启动我们的服务器,服务器启动的时候回向服务容器中注册我们的服务;(tips:这里我只是注册了一个服务实现类本身,如果结合之前我说过的日志记录以及事务处理等等,需要将服务实现类本身换成动态代理类),我这里并没有实现服务器的多线程处理,如果需要同时为多个客户端服务,需要将sk = ss.accept();获取到的Socket对象放进一个新的Thread进行操作,也就是说每当有一个client连接到服务器,服务器就新建一个Thread对该客户端进行服务!NC服务端就是采用的这种方式,同步阻塞IO,伪异步线程池;
- 客户端代码:
HClient
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82package rpcclient;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;
public class Client {
// 获取代表服务端接口HelloService的动态代理对象
/*
* @param
*/
public static <T> T getRemoteProxyObj(final String serviceName, final InetSocketAddress addr) throws ClassNotFoundException {
final Class<?> forName = Class.forName(serviceName);
@SuppressWarnings("unchecked")
T newProxyInstance = (T) Proxy.newProxyInstance(forName.getClassLoader(), new Class[] { forName },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
Socket sk = new Socket();
OutputStream os = null;
InputStream is = null;
try{
sk.connect(addr);
os = sk.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeUTF(forName.getSimpleName());
oos.writeUTF(method.getName());
oos.writeObject(method.getParameterTypes());
oos.writeObject(args);
oos.flush();
is = sk.getInputStream();
ObjectInputStream ois = new ObjectInputStream(is);
Object result = ois.readObject();
return result;
}catch(Exception e){
return null;
}finally{
if(os!=null){
try {
os.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(is!=null){
try {
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
sk.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
return newProxyInstance;
}
public static void main(String[] args) throws ClassNotFoundException{
/*由于我这里演示是本机演示,没有通过ngrok外网映射,如果你打通了外网映射,你就可以实现你的对外服务器啦*/
/*HelloService remoteProxyObj = (HelloService)getRemoteProxyObj("rpcclient.HelloService",new InetSocketAddress("free.idcfengye.com",18034));*/
HelloService remoteProxyObj = (HelloService)getRemoteProxyObj("rpcclient.HelloService",new InetSocketAddress("127.0.0.1",9999));
String result = remoteProxyObj.hello("hahaha");
System.out.println(result);
}
}客户端服务存根HelloService(这也就解释了我们的NC的接口代码为什么要放在public下面,因为客户端需要这个类的信息)
1
2
3
4
5package rpcclient;
public interface HelloService {
public String hello(String name);
}
以上就是我们的客户端代码,我们客户端只放了一个服务方法存根HelloService,那么我们是如何做到向服务端发送请求的了?答案就是动态代理,我们在获取HelloService对象的时候其实就是对该接口的匿名内部类实现进行了动态代理,然后再通过动态代理去向服务端发送请求,服务端拿到我们的请求方法名和请求参数等等去容器中寻找实现类并将执行结果返回到客户端,至此一次完整的RMI请求结束;
第三节 运行结果演示
1、启动服务端main方法如下图:
2、启动客户端连接服务器,发出请求得到结果:
Slogan:学过几年技术,尘世间一枚不起眼的迷途小书童