RMI简单介绍

版权声明:版权没有,盗用不究 https://blog.csdn.net/liman65727/article/details/81737792

前言

这篇博客主要介绍RMI(Remote Method Invocation)远程方法调用,这个东西现在用的不多,只是作为分布式基础,进行一个简单了解,为了引出RPC的相关概念,并对RPC做一个简单认识,方便后面学习Dubbo

RMI简介

RMI全称是remote method invocation 远程方法调用,一种用于远程过程调用的应用程序编程接口,是纯java的网络分布式应用系统的核心解决方案之一。RMI使用Java远程消息交换协议JRMP(Java Remote Messaging Protocol)进行通信,JRMP是专门为Java对象制定的,对非Java语言开发的应用系统支持不足,不能与非java语言的对象进行通信。

RMI简单实现

目前了解的RMI好像有两种实现方式,一种是单独运行注册表,另一种是在服务器端运行注册表,由于本身这里的RMI只是为了方便自己理解RPC框架,这里不再纠结于那种方式,而是简单采用手动操作,能完成调用就算成功,至于深入理解,后面会补上。

1、建立服务端项目rmi-server,建立相应的包,然后编写对外的服务接口

/**
 * 
 * @author liman
 * @createtime 2018年8月16日
 * @contract 15528212893
 * @comment:
 * 创建远程接口,需要继承至remote
 */
public interface PersonService extends Remote{
	
	public List<PersonEntity> GetList() throws RemoteException;

}

该接口需要实现Remote接口,这个接口是个标记接口,由于远程方法调用的本质是网络通信,只是隐藏了底层实现。网络通信出现异常需要处理,所以所有的方法都必须要抛出RemoteException以说明该方法可能抛出网络通信异常。

2、编写远程方法接口实现类

package com.learn.rmi.self.JRMP;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.LinkedList;
import java.util.List;

/**
 * 
 * @author liman
 * @createtime 2018年8月16日
 * @contract 15528212893
 * @comment:
 * 远程服务接口的实现类,这个实现类需要继承UnicastRemoteObject并实现PersonService接口
 */
public class PersonServiceImpl extends UnicastRemoteObject implements PersonService{

	protected PersonServiceImpl() throws RemoteException {
		super();
	}

	public List<PersonEntity> GetList() throws RemoteException {
		System.out.println("Get Person Start!");
		List<PersonEntity> personList=new LinkedList<PersonEntity>();

		PersonEntity person1=new PersonEntity();
		person1.setAge(25);
		person1.setId(0);
		person1.setName("Leslie");
		personList.add(person1);

		PersonEntity person2=new PersonEntity();
		person2.setAge(25);
		person2.setId(1);
		person2.setName("Rose");
		personList.add(person2);

		return personList;
	}

}

该方法的方法参数和返回值会在网络上传输,所以必须要实现序列化接口。

3、建立客户端项目,rmi-client

将前两部的服务端接口代码(PersonService)和实体(PersonEntity)拷贝到客户端中,需要保证package路径与服务端一致。首先承认这步操作有点骚气,一般这个步骤在之前的开发中都会有java编译器自带的RMI注册服务完成。

4、编写服务端的服务发布程序

package com.learn.rmi.self.JRMP;

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

/**
 * 
 * @author liman
 * @createtime 2018年8月16日
 * @contract 15528212893
 * @comment:
 * 服务端注册通讯端口
 */
public class ServerProgram {
	public static void main(String[] args) {
		try {
			PersonService personService = new PersonServiceImpl();
			
			//注册通讯端口
			LocateRegistry.createRegistry(6600);
			//注册通讯路径
			Naming.rebind("rmi://127.0.0.1:6600/PersonService", personService);
			
			System.out.println("Service start");
		} catch (Exception e) {
			e.printStackTrace();
		} 
	}
}

LocateRegistry.createRegistry(6600);——注册通讯端口。Naming.rebind()——注册通讯路径。

5、编写客户端代码

在确认将服务端的service接口和对应的实体类拷贝过来之后,编写客户端代码

package com.learn.rmi.self.JRMP;

import java.rmi.Naming;
import java.util.List;

/**
 * 
 * @author liman
 * @createtime 2018年8月16日
 * @contract 15528212893
 * @comment:
 * 客户端进行测试
 */
public class ClientProgram {
	public static void main(String[] args) {
		try {
			PersonService personService = (PersonService)Naming.lookup("rmi://127.0.0.1:6600/PersonService");
			
			List<PersonEntity> personList = personService.GetList();
			
			for(PersonEntity person:personList) {
				System.out.println("ID:"+person.getId()+" Age:"+person.getAge()+" Name:"+person.getName());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

6、测试运行

先运行服务端代码,完成之后控制台输出如下结果,表示服务端启动成功

然后启动客户端:

 客户端没有维护PersonService的逻辑,只是保留了服务端的接口信息,客户端能成功调用相应的服务。不过有几个细节需要注意。

1、涉及到的序列化对象需要实现Serializable接口,这里的就是PersonEntity对象。

2、客户端和服务端PersonEntity中的serialVersionUID的值要一致,否则会抛出二者不一致的异常。关于serialVersionUID这个字段的作用,在序列化一文中有过总结,但是不够全面,这里可以参看这篇文章——serialVersionUID字段的作用

RMI底层通信(取消注册中心)

其实RMI的原理和RPC框架很像,前几天在看源码的过程中,终于成功被源码绕晕,看来还是先只能简单实现,源码的理解后面再深入。

分为客户端和服务端,底层实质就是序列化和反序列化的通信。

客户端代码

1、客户端测试代码

主要通过获取服务端远程的对象和调用结果

package com.learn.rmi.rpc;

/**
 * Created by liman on 2018/8/18.
 * QQ:657271181
 * e-mail:[email protected]
 */
public class ClientDemo {

    public static void main(String[] args) {
        RpcClientProxy rpcClientProxy = new RpcClientProxy();

        ILearnHello learnHello = rpcClientProxy.clientProxy(ILearnHello.class,"localhost",8888);
        System.out.println(learnHello.sayHello("liman"));
    }

}

2、RpcClientProxy代理类

package com.learn.rmi.rpc;

import com.learn.rmi.ref.rpc.RemoteInvocationHandler;

import java.lang.reflect.Proxy;

/**
 * Created by liman on 2018/8/18.
 * QQ:657271181
 * e-mail:[email protected]
 *
 * 客户端的代理
 */
public class RpcClientProxy {

    public <T> T clientProxy(final Class<T> interfaceClass,final String host,final int port){
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
                new Class[]{interfaceClass}, new RemoteInvocationHandler(host,port));
    }

}

3、RemoteInvocationHandler类,设置请求对象中需要调用的目标方法和参数,并完成请求的发送和结果获取

package com.learn.rmi.rpc;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * Created by liman on 2018/8/18.
 * QQ:657271181
 * e-mail:[email protected]
 *
 * 需要发起客户端和服务端的远程调用。
 */
public class RemoteInvocationHandler implements InvocationHandler{

    private String host;
    private int port;

    public RemoteInvocationHandler(String host, int port) {
        this.host = host;
        this.port = port;
    }

    /**
     * 需要跟远程发起调用
     * 设置了需要调用的远程方法信息,然后发布到远程
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    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);

        TCPTransport tcpTransport = new TCPTransport(this.host,this.port);

        return tcpTransport.send(request);
    }
}

4、TCPTransport,真正的请求发送

package com.learn.rmi.rpc;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

/**
 * Created by liman on 2018/8/18.
 * QQ:657271181
 * e-mail:[email protected]
 * <p>
 * 专门管理socket
 */
public class TCPTransport {

    private String host;
    private int port;

    public TCPTransport(String host, int port) {
        this.host = host;
        this.port = port;
    }

    //创建一个新的连接
    private Socket newSocket() {
        System.out.println("创建一个新连接");

        Socket socket = null;
        try {
            socket = new Socket(host, port);
            return socket;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("创建连接异常!");
        }
    }

    public Object send(RpcRequest request) {
        Socket socket = null;
        try {
            socket = newSocket();
            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("发起服务调用异常");
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

5、远程目标的接口

package com.learn.rmi.rpc;

/**
 * Created by liman on 2018/8/18.
 * QQ:657271181
 * e-mail:[email protected]
 */
public interface ILearnHello {

    public String sayHello(String msg);

}

6、请求对象,客户端主要完成的任务就是封装一个RPCRequest请求对象,然后发送到远程服务器,这不操作在真正的RPC框架中并不是这么实现的,后续需要深入学习这一点。

package com.learn.rmi.rpc;

import java.io.Serializable;

/**
 * Created by liman on 2018/8/18.
 * QQ:657271181
 * e-mail:[email protected]
 *
 * 传输对象
 *
 */
public class RpcRequest implements Serializable{

    private static final long serialVersionUID = 903071532920235263L;

    private String className;
    private String methodName;
    private Object[] parameters;

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Object[] getParameters() {
        return parameters;
    }

    public void setParameters(Object[] parameters) {
        this.parameters = parameters;
    }
}

服务端代码

1、业务接口

package com.learn.rmi.rpc;

/**
 * Created by liman on 2018/8/18.
 * QQ:657271181
 * e-mail:[email protected]
 */
public interface ILearnHello {

    public String sayHello(String msg);

}

2、业务接口实现类,这个和接口都需要放在与客户端同一个目录名称下

package com.learn.rmi.rpc;

/**
 * Created by liman on 2018/8/18.
 * QQ:657271181
 * e-mail:[email protected]
 */
public class LearnHelloImpl implements ILearnHello{

    @Override
    public String sayHello(String msg) {
        return "Hello "+msg+" this is remote service";
    }
}

3、RPCRequest,由于这里是最简陋的实现,需要服务端维护一个RPCRequest对象,同样需要与客户端放在相同目录下

package com.learn.rmi.rpc;

import java.io.Serializable;

/**
 * Created by liman on 2018/8/18.
 * QQ:657271181
 * e-mail:[email protected]
 *
 * 传输对象
 *
 */
public class RpcRequest implements Serializable{

    private static final long serialVersionUID = 903071532920235263L;

    private String className;
    private String methodName;
    private Object[] parameters;

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Object[] getParameters() {
        return parameters;
    }

    public void setParameters(Object[] parameters) {
        this.parameters = parameters;
    }
}

4、将服务发布到指定的端口上(即使在指定的端口上处理请求)

package com.learn.rmi.rpc;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by liman on 2018/8/18.
 * QQ:657271181
 * e-mail:[email protected]
 *
 * 负责将服务发布出去
 */
public class RpcServer {

    //定义一个线程池
    private final ExecutorService executorService = Executors.newCachedThreadPool();

    /**
     * 将指定的服务发布出去
     * @param service 待发布的服务
     * @param port 端口
     */
    public void publisher(final Object service,int port){
        ServerSocket serverSocket = null;
        try {
            //启动一个服务监听
            serverSocket = new ServerSocket(port);
            System.out.println("服务开启,等待客户端请求");
            //循环监听服务
            while(true){
                //循环处理客户端请求
                Socket socket = serverSocket.accept();
                executorService.execute(new ProcessorHandler(socket,service));
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(serverSocket!=null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

5、ProcessHandler类,真正的处理请求代码

package com.learn.rmi.rpc;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;

/**
 * Created by liman on 2018/8/18.
 * QQ:657271181
 * e-mail:[email protected]
 */
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 objectInputStream = null;
        try {
            objectInputStream = new ObjectInputStream(socket.getInputStream());

            //获取远程传输的对象,通过反序列化获取
            RpcRequest request = (RpcRequest) objectInputStream.readObject();

            //处理结果
            Object result = invoke(request);

            //将结果输出到客户端
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(result);

            //各种流的简单关闭,这里就没有在finally中关闭
            outputStream.flush();
            objectInputStream.close();
            outputStream.close();

        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            if(objectInputStream!=null){
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    /**
     * 通过反射获取客户端需要调用的方法,并将结果返回
     * @param request
     * @return
     */
    private Object invoke(RpcRequest request) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        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);
    }
}

6、服务端测试类

package com.learn.rmi.rpc;

/**
 * Created by liman on 2018/8/19.
 * QQ:657271181
 * e-mail:[email protected]
 */
public class ServerDemo {

    public static void main(String[] args) {
        ILearnHello iLearnHello = new LearnHelloImpl();

        RpcServer rpcServer = new RpcServer();
        rpcServer.publisher(iLearnHello,8889);
    }

}

运行结果

服务端运行结果

客户端运行结果

 说明

上述针对RMI的总结十分简单,后面针对RMI的实现也是十分简单,直接省去了注册中心这一步。后续需要学习深入

猜你喜欢

转载自blog.csdn.net/liman65727/article/details/81737792
Rmi