1.Java RMI
RMI(Remote Method Invocation)远程方法调用,其目的是让客户端可以像调用本地对象方法那样调用服务端java对象
原理如图所示:
具体的实现步骤:
1、客户调用客户端辅助对象stub上的方法
2、客户端辅助对象stub打包调用信息(变量、方法名),通过网络发送给服务端辅助对象skeleton
3、服务端辅助对象skeleton将客户端辅助对象发送来的信息解包,找出真正被调用的方法以及该方法所在对象
4、调用真正服务对象上的真正方法,并将结果返回给服务端辅助对象skeleton
5、服务端辅助对象将结果打包,发送给客户端辅助对象stub
6、客户端辅助对象将返回值解包,返回给调用者
7、客户获得返回值
这里有几个技术关键点:
(1)客户端和服务端之间是如何传输信息的?传输的信息是什么?
传输信息的渠道是socket流,传输的信息是客户端想要调用服务端的类,方法,参数的序列化数据
(2)服务端接收到客户端的信息之后是如何找到客户端想要调用的方法的?
服务端接收到传递过来的数据之后,根据指定的类,方法,参数反射调用对应的方法,然后将方法的返回结果写入socket流
(3)客户端是如何把想要调用服务端的类,方法,参数等数据进行序列化的?
客户端使用的是动态代理技术,这样既可以避免想要调用的类没有实现类而报错,又可以在调用对应的方法的时候,获取到这个方法的所有信息,进而把这些信息封装起来进行序列化传输
那么我们要实现一个RMI框架基本上也是按照上面的流程
通过上面的介绍基本上我们对整个流程和需要使用的技术都己经清楚了,那么接下来我们就手动实现一个RMI框架
首先我们需要服务器端向外提供一个方法以及其实现类
public interface IHello {
String sayHello(String msg);
}
//实现类
public class HelloImpl implements IHello {
@Override
public String sayHello(String msg) {
return "Hello , "+msg;
}
}
我们需要将上面的IHello中的sayHello方法暴露出来给客户端调用,那么就需要一个发布者RpcService负责发布一个远程服务
public class RpcService {
//创建一个线程池
private static final ExecutorService executorService=Executors.newCachedThreadPool();
/**
* 绑定服务和对应端口
*/
public void publisher(final Object service,int port){
ServerSocket serverSocket=null;
try{
serverSocket=new ServerSocket(port); //启动一个服务监听
while(true){ //循环监听
Socket socket=serverSocket.accept(); //监听服务
//通过线程池去处理请求
executorService.execute(new ProcessorHandler(socket,service));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
// ... close
}
}
}
创建一个处理客户端sokcet请求并反射调用对应的服务端方法的类
public class ProcessorHandler implements Runnable{
private Socket socket;
private Object service; //服务端发布的服务
public ProcessorHandler(Socket socket, Object service) {
this.socket = socket;
this.service = service;
}
@Override
public void run() {
//处理请求
ObjectInputStream inputStream=null;
try {
//获取客户端的输入流
inputStream = new ObjectInputStream(socket.getInputStream());
//反序列化远程传输的对象RpcRequest
RpcRequest request=(RpcRequest) inputStream.readObject();
Object result = invoke(request); //通过反射去调用本地的方法
//通过输出流讲结果输出给客户端
ObjectOutputStream outputStream=new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(result);
outputStream.flush();
inputStream.close();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}finally {
if(inputStream!=null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 通过解析远程方法调用传递的参数信息,反射调用对应的方法
*/
private Object invoke(RpcRequest request) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
//以下均为反射操作,目的是通过反射调用服务
Object[] args = request.getParameters();
Class<?>[] types=new Class[args.length];
for(int i=0;i<args.length;i++){
types[i]=args[i].getClass();
}
Method method = service.getClass().getMethod(request.getMethodName(),types);
return method.invoke(service,args);
}
}
public class RpcRequest implements Serializable {
private static final long serialVersionUID = -9100883062391457993L;
private String className;
private String methodName;
private Object[] parameters;
}
如此服务器端的流程已经走完
我们可以使用如下代码来发布一个服务
public class ServerDemo {
public static void main(String[] args) throws Exception {
IHello iHello =new HelloImpl();
RpcService rpcService =new RpcService();
rpcService.publisher(iHello,8888);
}
}
执行之后服务端已经存在了一个监听8888端口的服务等待着客户端的调用,接下来我们编写客户端的代码:
首先是将要调用的服务端的方法:
public interface IHello {
String sayHello(String msg);
}
上面服务端我们看到了是根据客户端传递过来的被调用的方法的信息进行反射调用的,那么客户端需要做的就是使用动态代理拿到将要调用的服务方法的信息
public class RpcClientProxy {
/**
* 使用动态代理获取被调用的方法信息()
*/
public <T> T clientProxy(final Class<T> interfaceCls,
final String host,final int port){
//使用到了动态代理。
return (T)Proxy.newProxyInstance(interfaceCls.getClassLoader(),
new Class[]{interfaceCls},new RemoteInvocationHandler(host,port));
}
}
动态代理获取到被调用方法的参数,封装成一个RPC请求
public class RemoteInvocationHandler implements InvocationHandler {
private String host;
private int port;
public RemoteInvocationHandler(String host, int port) {
this.host = host;
this.port = port;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//组装请求
RpcRequest request = new RpcRequest();
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParameters(args);
//通过tcp传输协议进行传输
TCPTransport tcpTransport = new TCPTransport(this.host,this.port);
//发送请求
return tcpTransport.send(request);
}
}
//封装一个RPC请求(需要进行网络传递因此需要序列化,并且需要和服务端的序列化id保持一致)
public class RpcRequest implements Serializable {
private static final long serialVersionUID = -9100883062391457993L;
private String className;
private String methodName;
private Object[] parameters;
}
客户端拿到了被调用方法的信息之后需要和服务端建立socket连接,将信息传递给服务端
public class TCPTransport {
private String host;
private int port;
public TCPTransport(String host, int port) {
this.host = host;
this.port = port;
}
//创建一个socket连接
private Socket newSocket(){
System.out.println("创建一个新的连接");
Socket socket;
try{
socket=new Socket(host,port);
return socket;
}catch (Exception e){
throw new RuntimeException("连接建立失败");
}
}
public Object send(RpcRequest request){
Socket socket=null;
try {
socket = newSocket();
//获取输出流,将客户端需要调用的远程方法参数request发送给
ObjectOutputStream outputStream=new ObjectOutputStream
(socket.getOutputStream());
outputStream.writeObject(request);
outputStream.flush();
//获取输入流,得到服务端的返回结果
ObjectInputStream inputStream=new ObjectInputStream
(socket.getInputStream());
Object result=inputStream.readObject();
inputStream.close();
outputStream.close();
return result;
}catch (Exception e ){
throw new RuntimeException("发起远程调用异常:",e);
}finally {
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
客户端调用方法;
public class ClientDemo {
public static void main(String[] args) {
RpcClientProxy rpcClientProxy=new RpcClientProxy();
IHello hello= rpcClientProxy.clientProxy
(IHello.class,"localhost",8888);
System.out.println(hello.sayHello("mic"));
}
}
执行客户端代码客户端会执行到IHello的动态代理方法,在invoke方法中客户端封装了请求的参数并使用socket流和服务器端建立了连接,将调用信息传递给服务器,服务器接收到请求之后,解读数据使用反射调用对应的服务的方法,执行之后将结果通过socket流传递给客户端,完成远程方法调用
当然上面的demo只是一个对RMI的简单的实现,并没有注册中心等等的组件,但是相信对于我们理解RMI的底层原理有很好的帮助
来源:https://blog.csdn.net/wang2963973852/article/details/100145386