• 网络编程——分布式远程调用


    远程调用方案

    1. RMI

    远程方法调用(Remote Method Invocation) 只适用于java语言
    在本地调用,操作的是服务器端口在本地的副本,通过stub来重放服务器的端口;服务端也设置了一个扶助对象skeleton。实质上是stub和skeleton在直接交互
    因此,RMI必须将服务端的接口在本地复制一份

    2. RPC

    远程过程调用(Remote Procedure Call Protocol) 跨平台、跨语言,客户端和服务器端之间通过网络服务协议来进行请求和响应

    案例:实现自定义的RMI

    实现思路:

    (1) 客户端和服务器端通过socket交互信息
    1. 客户端以字符串的形式定义好要请求的接口名字,然后通过反射,解析出请求的接口名、方法名、参数类型、参数值等,并将这些信息通过对象流ObjectOutputStream发送给服务端
    2. 服务端逐个解析这些信息,并通过反射中的invoke()方法调用服务端上被请求的方法
    (2) 服务端可能存在多个提供方法的接口,简单实现一个服务注册中心
    (3)服务注册中心
    (4)服务完毕后,将返回值通过对象流返回给客户端
    (5)客户端需要通过动态代理来获取服务端的返回值

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    1. 服务端:提供服务的接口
    package rmi.server;
    
    /**
     * @ClassName: RMIService
     * @Description: 服务端的服务
     * @Author sunsl
     * @Date 2022/9/11 21:06
     * @Version 1.0
     */
    public interface RMIService {
        public String hello(String name);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. 服务端:实现类
    package rmi.server;
    
    /**
     * @ClassName: RMIServiceImpl
     * @Description: TODO
     * @Author sunsl
     * @Date 2022/9/11 21:46
     * @Version 1.0
     */
    public class RMIServiceImpl implements RMIService {
        @Override
        public String hello(String name) {
            return "服务端已收到请求,你好," + name;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    1. 服务端:注册中心接口
    package rmi.server;
    
    /**
     * @ClassName: RMIEureka
     * @Description: 简单的服务注册中心
     * @Author sunsl
     * @Date 2022/9/11 21:48
     * @Version 1.0
     */
    public interface RMIEureka {
        //启动服务
        public void start() ;
        //关闭服务
        public void stop();
        //注册服务
        public void register(Class service, Class serviceImpl);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    1. 服务端:实现类
    package rmi.server;
    
    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.InetSocketAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.HashMap;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * @ClassName: RMIEurekaImpl
     * @Description: 服务注册中心具体实现
     * @Author sunsl
     * @Date 2022/9/11 21:48
     * @Version 1.0
     */
    public class RMIEurekaImpl implements RMIEureka {
        // map:服务端的所有可供客户端访问的接口,都注册到该map中
        // key:接口的名字(如"RMIService"),
        // value:真正的提供服务的类(如RMIServiceImpl类)
        private static HashMap<String, Class> register = new HashMap<>();
    
        //服务端的端口号
        private static int port;
    
        //线程池,获取当前环境CPU核数
        private static ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    
        //是否开启服务
        private static volatile boolean isRunning = false;
    
        // 构造函数,传入端口号
        public RMIEurekaImpl(int port) {
            this.port = port;
        }
    
        // 开启服务端服务
        @Override
        public void start() {
            ServerSocket serverSocket = null;
            try {
                serverSocket = new ServerSocket();
                // 绑定IP地址
                serverSocket.bind(new InetSocketAddress(port));
            } catch (IOException e) {
                e.printStackTrace();
            }
            isRunning = true;
            //客户端每次请求一次请求,服务端从线程池中启动一个线程对象去处理
            while (true){
                //具体的服务内容:接收客户端请求,处理请求,并返回结果
                System.out.println("服务已启动...");
                Socket socket = null;
                try {
                    //The method blocks until a connection is made.
                    socket = serverSocket.accept();// 等待客户端连接
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //启动一个线程 去处理客户请求
                threadPool.execute(new ServiceTask(socket));
            }
        }
    
        @Override
        public void stop() {
            isRunning = false;
            threadPool.shutdown();
        }
    
        //将接口名和接口实现类一一对应,以便于接收到请求时,能及时获取到对应的服务实现类
        @Override
        public void register(Class service, Class serviceImpl) {
            register.put(service.getName(), serviceImpl);
        }
    
        //处理请求的线程
        private static class ServiceTask implements Runnable {
            private Socket socket;
    
            public ServiceTask() {
            }
    
            public ServiceTask(Socket socket) {
                this.socket = socket;
            }
    
            // 处理逻辑
            @Override
            public void run() {
                ObjectInputStream inputStream = null;
                ObjectOutputStream outputStream = null;
                try {
                    //接收客户端请求的参数
                    inputStream = new ObjectInputStream(socket.getInputStream());
    
                    // 因为ObjectInputStream对发送数据的顺序有严格要求,因此必须按照发送的顺序逐个接收
                    // 请求的接口名
                    String serviceName = inputStream.readUTF();
                    String methodName = inputStream.readUTF();
                    Class<?>[] parameterTypes = ( Class<?>[]) inputStream.readObject();
                    Object[] args = (Object []) inputStream.readObject();
    
                    // 根据客户请求,到服务注册中心map中找到与之对应的具体接口(即RMIService)
                    Class ServiceClass = register.get(serviceName);
                    // 构造请求的方法
                    Method method = ServiceClass.getMethod(methodName, parameterTypes);
                    // 执行该服务端方法
                    Object result = method.invoke(ServiceClass.newInstance(), args);
    
                    // 执行完成后返回给客户端
                    outputStream = new ObjectOutputStream(socket.getOutputStream());
                    outputStream.writeObject(result);
    
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (outputStream != null)
                            outputStream.close();
                        if (inputStream != null)
                            inputStream.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    
    • 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
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    1. 客户端:
    package rmi.client;
    
    /**
     * @ClassName: RMIClient
     * @Description: RMI客户端,发送请求,接收服务端返回的数据(代理模式
     * @Author sunsl
     * @Date 2022/9/11 21:05
     * @Version 1.0
     */
    
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.net.InetSocketAddress;
    import java.net.Socket;
    
    @SuppressWarnings("all")
    public class RMIClient {
        /**
         * 获取代表服务端接口的动态代理对象(RMIService)
         * @param serviceInterface 请求的接口名
         * @param addr 待请求服务端的ip:端口
         * @param 
         * @return
         */
        public static <T> T getRemoteProxyObj(Class serviceInterface, InetSocketAddress addr) {
            /**
    		   newProxyInstance(a,b,c)中,前两个参数的含义如下:
    		   	a:类加载器 :需要代理那个对象的类加载器
    		    b:用于表示需要代理的对象提供了哪些方法。Java是单继承、多实现,
    		    	因此,如果某一个对象实现了多个接口,那么该对象就拥有其全部接口的所有方法,因此是一个接口数组。
    		        例如,如果有A implements B接口,c接口,并且B接口中有3个方法,C接口中有2个方法,那么A的对象就拥有5个方法(暂不考虑A自身提供的方法)
    		 */
            return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(),
                    new Class<?>[]{serviceInterface},
                    new InvocationHandler() {
                        //proxy:代理的对象, method:哪个方法(sayHi(参数列表)), args:参数列表
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            // 客户端向服务端发送请求,请求某一个具体的接口
                            Socket socket = new Socket();
                            ObjectOutputStream output = null;
                            ObjectInputStream input = null;
                            try {
                                //请求地址
                                socket.connect(addr);
    
                                //通过序列化流向服务端发送请求
                                output = new ObjectOutputStream(socket.getOutputStream());
                                //发送请求的接口名、方法名、参数类型、参数值
                                output.writeUTF(serviceInterface.getName());
                                output.writeUTF(method.getName());
                                output.writeObject(method.getParameterTypes());
                                output.writeObject(args);
    
                                //等待处理...
                                //接收处理后返回的返回值
                                input = new ObjectInputStream(socket.getInputStream());
                                return input.readObject();
                            } catch (Exception e) {
                                e.printStackTrace();
                                return null;
                            } finally {
                                // 关闭打开的流
                                try {
                                    if (output != null) output.close();
                                    if (input != null) input.close();
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    });
        }
    
    
    }
    
    
    • 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
    1. 服务端启动类:
    package rmi.test;
    
    import rmi.server.RMIEurekaImpl;
    import rmi.server.RMIServiceImpl;
    import rmi.server.RMIServiceImpl2;
    import rmi.server.RMIService;
    
    /**
     * @ClassName: TestServer
     * @Description: 开启服务端
     * @Author sunsl
     * @Date 2022/9/11 22:35
     * @Version 1.0
     */
    public class TestServer {
        public static void main(String[] args) {
            //用线程的形式启动服务
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //服务中心
                    RMIEurekaImpl server = new RMIEurekaImpl(9999);
                    //将RMIService接口及实现类,注册到服务中心
                    server.register(RMIService.class, RMIServiceImpl.class);
                    server.start();
                }
            }).start();
        }
    }
    
    
    • 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
    1. 客户端启动类:
    package rmi.test;
    
    import rmi.client.RMIClient;
    import rmi.server.RMIService;
    
    import java.net.InetSocketAddress;
    
    /**
     * @ClassName: TestClient
     * @Description: 开启客户端
     * @Author sunsl
     * @Date 2022/9/11 22:27
     * @Version 1.0
     */
    public class TestClient {
        public static void main(String[] args) throws ClassNotFoundException {
            //调用远程的rmi.server.RMIService接口,并执行接口中的sayHi()方法
            RMIService remoteProxyObj = RMIClient.getRemoteProxyObj(
                    Class.forName("rmi.server.RMIService"),
                    new InetSocketAddress("127.0.0.1", 9999)
            );
            System.out.println(remoteProxyObj.hello("sunsl"));
        }
    }
    
    
    • 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

    在这里插入图片描述

  • 相关阅读:
    【云原生】详解Kubernetes、详解k8s,什么是k8s,k8s有什么用;k8s集群是什么、有什么用?
    iPad Pro 2022款将迎来大改款 支持反向无线充电
    5.4版本内核ufs设备信息查询方式
    python使用Augmentor对图像分割数据集中原图和标签mask同时进行变换
    线性回归(linear regression)
    前端下载文件
    RNN和注意力机制是啥关系
    Python基础入门例程6-NP6 牛牛的小数输出
    Blazor前后端框架Known-V1.2.2
    java - 网络编程TCP/IP
  • 原文地址:https://blog.csdn.net/Lionel_SSL/article/details/126811002