分布式通讯框架-RMI原理分析

什么是 RPC
RPC(Remote Procedure Call,远程过程调用),一般用来实现部署在不同机器上的系统之间的方法调用,使得程序能够像访问本地系统资源一样,通过网络传输去访问远端系统资源。对于客户端来说, 传输层使用什么协议,序列化、反序列化都是透明的。

了解 Java RMI
RMI 全称是 remote method invocation – 远程方法调用,一种用于远程过程调用的应用程序编程接口,是纯 java 的网络分布式应用系统的核心解决方案之一。
RMI 目前使用 Java 远程消息交换协议 JRMP(Java Remote Messageing Protocol)进行通信,由于 JRMP 是专为 Java对象制定的,是分布式应用系统的百分之百纯 java 解决方案,用 Java RMI 开发的应用系统可以部署在任何支持 JRE的平台上,缺点是,由于 JRMP 是专门为 java 对象指定的,因此 RMI 对于非 JAVA 语言开发的应用系统的支持不足,不能与非 JAVA 语言编写的对象进行通信。

个人理解 RPC 和 RMI 的区别
RPC是一个跨进程远程调用的思想,它的初衷就是实现两个应用之间的通信。而实现RPC的方式会有很多种,RMI其实就是RPC具体的实现方式之一。

Java RMI 代码实践

//普通接口继承 Remote
public interface IHelloService extends Remote{

    String sayHello(String msg) throws RemoteException;
}

//接口实现类继承 UnicastRemoteObject 
public class HelloServiceImpl extends UnicastRemoteObject implements IHelloService {

    @Override
    public String sayHello(String msg) throws RemoteException{
        return "Hello,"+msg;
    }

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

//服务端发布 helloService 实例
public class Server {

    public static void main(String[] args) {
        try {
            IHelloService helloService=new HelloServiceImpl();
            LocateRegistry.createRegistry(1099);
            Naming.bind("rmi://127.0.0.1/Hello",helloService);
            System.out.println("服务启动成功...");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

//客户端通过服务端发布的地址,拿到helloService实例,并且调用sayHello方法
//(完成了跨进程之间的通信)
public class ClientDemo {

    public static void main(String[] args) {
        try {
            IHelloService helloService=(IHelloService) Naming.lookup("rmi://127.0.0.1/Hello");
            System.out.println(helloService.sayHello("我是架构师"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

远程对象必须实现 UnicastRemoteObject,这样才能保证客户端访问获得远程对象时,该远程对象会把自身的一个拷贝以 Socket 形式传输给客户端,客户端获得的拷贝称为“stub” , 而服务端本身已经存在的远程对象称为“skeleton”,此时客户端的stub 是客户端的一个代理,用于与服务器端进行通信,而 skeleton 是服务端的一个代理,用于接收客户端的请求之后调用远程方法来响应客户端的请求。

自定义 RMI 框架
服务端代码:
1》服务端对外发布服务并提供端口号。

public class ServerDemo {

    public static void main(String[] args) {
        TaofutHelloWorld taofutHelloWorld=new TaofutHelloWorldImpl();
        RpcServer rpcServer=new RpcServer();
        //对外发布服务并提供端口号8888
        rpcServer.publisher(taofutHelloWorld,8888);

    }
}

2》具体进去看看rpcServer做了什么。

/**
 * 用来对外发布一个服务,并且监听客户端发来的请求
 */
public class RpcServer {

    //定义一个线程池
    private static 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();
                }
            }
        }
    }
}

3》继续跟进线程处理的任务。

/**
 * 处理客户端发来的请求
 */
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() {
        //处理socket请求,服务端接收数据,从输入流里读
        ObjectInputStream objectInputStream=null;
        ObjectOutputStream outputStream=null;
        try {
            objectInputStream=new ObjectInputStream(socket.getInputStream());
            RpcRequest rpcRequest=(RpcRequest) objectInputStream.readObject();
            Object result=invoke(rpcRequest);

            //将服务端执行的结果 通过socket回传给客户端
            outputStream=new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(result);
            outputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(objectInputStream!=null){
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 客户端:发来请求告诉服务端,我要调用你这个服务里面的xx方法,并将xx方法的详细信息传递给服务端。
     * 服务端:收到请求及参数后,利用反射去执行了xx方法,并将执行的结果返回给客户端。
     * @param rpcRequest
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws NoSuchMethodException
     */
    private Object invoke(RpcRequest rpcRequest) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        Object[] args=rpcRequest.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(rpcRequest.getMethodName(),types);
        return method.invoke(service,args);
    }
}

4》服务端业务接口及实现类。

public interface TaofutHelloWorld {

    String sayHello(String msg);
}

public class TaofutHelloWorldImpl implements TaofutHelloWorld {

    @Override
    public String sayHello(String msg) {
        return "Hello,World! My name is taofut "+msg;
    }
}

5》服务端接收到客户端的传输对象。

/**
 * 传输对象
 */
public class RpcRequest implements Serializable{

    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》客户端向服务端发起请求。

public class ClientDemo {

    public static void main(String[] args) {
        RpcClientProxy rpcClientProxy=new RpcClientProxy();
        //客户端使用代理类来发起请求
        TaofutHelloWorld taofutHelloWorld=(TaofutHelloWorld) rpcClientProxy.clientProxy(TaofutHelloWorld.class,"127.0.0.1",8888);
        System.out.println(taofutHelloWorld.sayHello("我是架构师"));
    }
}

2》创建客户端代理类。

/**
 * 创建一个客户端代理类
 */
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));
    }
}

3》看看代理类具体的逻辑。

public class RemoteInvocationHandler implements InvocationHandler {

    private String host;
    private int port;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RpcRequest rpcRequest=new RpcRequest();
        rpcRequest.setClassName(method.getDeclaringClass().getName());
        rpcRequest.setMethodName(method.getName());
        rpcRequest.setParameters(args);
        //传输
        TCPTransport tcpTransport=new TCPTransport(host,port);
        //向服务端发送请求,并且传输 rpcRequest
        return tcpTransport.send(rpcRequest);
    }

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

4》看看 tcpTransport 的具体实现。

public class TCPTransport {
    private String host;
    private int port;

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

    public Socket newSocket(){
        System.out.println("创建一个新的连接");
        Socket socket=null;
        try {
            //与服务端建立连接
            socket=new Socket(host,port);
            return socket;
        } catch (IOException e) {
            throw new RuntimeException("连接建立失败");
        }

    }

    /**
     * 与服务端建立连接,并且向服务端传输 rpcRequest(服务端需要执行的方法详细信息)
     * 收到服务端执行完方法的结果
     * @param rpcRequest
     * @return
     */
    public Object send(RpcRequest rpcRequest){
        Socket socket=null;
        try {
            socket=newSocket();
            ObjectOutputStream outputStream=new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(rpcRequest);//rpcRequest序列化传输
            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();
                }
            }
        }
    }
}

5》客户端向服务端传输的对象 rpcRequest

public class RpcRequest implements Serializable{

    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;
    }
}

当客户端执行 taofutHelloWorld.sayHello(“我是架构师”),代理类就会向服务端发起请求并传递参数,服务端执行完方法后,将结果返回给客户端。客户端打印出结果到控制台:Hello,World! My name is taofut 我是架构师

客户端服务端处理过程图
这里写图片描述

猜你喜欢

转载自blog.csdn.net/fu123123fu/article/details/80641517