通过socket、多线程、动态代理、反射 实现RPC远程方法调用

一、概念梳理

1、Socket是什么?

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

Socket技术详解

2、动态代理是什么?

目前java动态代理的实现分为两种

1.基于JDK的动态代理

2.基于CGILB的动态代理

在业务中使用动态代理,一般是为了给需要实现的方法添加预处理或者添加后续操作,但是不干预实现类的正常业务,把一些基本业务和主要的业务逻辑分离。我们一般所熟知的Spring的AOP原理就是基于动态代理实现的。

【Java知识点详解 2】动态代理

3、反射是什么?

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

【Java知识点详解 8】反射

4、RPC是什么?

RPC是远程过程调用的简称,广泛应用在大规模分布式应用中,作用是有助于系统的垂直拆分,使系统更易拓展。Java中的RPC框架比较多,各有特色,广泛使用的有RMI、Hessian、Dubbo等。RPC还有一个特点就是能够跨语言。

RPC服务和HTTP服务对比

二、模拟RPC远程方法调用

1、思路

  1. 客户端通过socket请求服务端,并且通过字符串形式,将需要的接口发送给服务端(通过动态代理发送接口名、方法名) 。
  2. 服务端将可以提供的接口注册到服务中心(通过map保存,key为接口的名字,value为接口的实现类)。
  3. 服务端接收到客户端的请求后,通过请求的接口名在服务中心的map中寻找对应的接口实现类 找到之后,解析刚才客户端发过来的接口名、方法名,解析完毕后,通过反射技术将该方法执行,执行完毕后,再讲该方法的返回值返回给客户端。

2、分析草图

​三、代码实现

1、接口

package com.guor.rpc.server;

public interface HelloService {
    String sayHello(String name);
}

2、接口实现类 

package com.guor.rpc.server;

public class HelloServiceImpl implements HelloService{
    @Override
    public String sayHello(String name) {
        System.out.println("hello " + name);
        return "hello " + name;
    }
}

3、socket服务端(多线程、反射)

package com.guor.rpc.server;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 大致思路:
 * 1、角色1:客户端、角色2:发布服务的接口(服务端)、角色3:服务的注册中心;
 * 2、服务端通过register方法将接口注册到注册中心,key为关键字,value为接口的具体实现;
 * 3、客户端与服务端通过socket进行通信,通过反射技术,客户端发送一个字符串到注册中心,
获取注册中心中map对应的值,即服务端接口的一切信息;
 * 4、客户端通过动态代理对象接收不同的接口类型
 */
public class ServerCenterImpl implements  ServerCenter{
    //服务端的所有可供客户端访问的接口都注册到map中
    private static Map<String, Class> serverRegister = new HashMap<String, Class>();
    private static int port;
    public ServerCenterImpl(int port){
        this.port = port;
    }

    //连接池,一个对象处理一个客户请求(连接池中大小与CPU有关)
    private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    private static boolean isRunning = false;

    //开启服务端服务
    @Override
    public void start() {
        try {
            ServerSocket serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(port));
            isRunning = true;
            while(true){
                System.out.println("服务端 start...");
                //接收客户端请求,处理请求,并返回结果
                //客户端没发送一次请求,则服务端从连接池中获取一个线程
                Socket socket = serverSocket.accept();//等待客户端连接
                executor.execute(new ServiceTask(socket));
            }
        }catch (Exception e){
            System.out.println("start exception." + e);
        }
    }

    @Override
    public void close() {
        isRunning = false;
        executor.shutdown();
    }

    //服务端通过register方法将接口注册到注册中心,key为关键字,value为接口的具体实现;
    @Override
    public void register(Class servie, Class serviceImpl) {
        serverRegister.put(servie.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 input = null;
            ObjectOutputStream output = null;
            try{
                //因为ObjectOutputStream对发送数据的顺序严格要求,因此需要参照发送的顺序逐个接收
                input = new ObjectInputStream(socket.getInputStream());
                //接口名
                String serviceName = input.readUTF();
                //方法名
                String methodName = input.readUTF();
                //方法参数类型
                Class[] parameterTypes = (Class[])input.readObject();
                //方法参数
                Object[] arguments = (Object[])input.readObject();
                //根据客户端请求,到map中找到与之对应的具体接口
                Class serviceClass = serverRegister.get(serviceName);
                Method method = serviceClass.getMethod(methodName, parameterTypes);
                Object result = method.invoke(serviceClass.newInstance(), arguments);// = person.say("hello");
                //将方法执行完毕的返回值传给客户端
                output = new ObjectOutputStream(socket.getOutputStream());
                output.writeObject(result);
            }catch (Exception e){
                System.out.println("ServerCenterImpl" + e);
            }finally {
                if(output!=null){
                    try{
                        output.close();
                    }catch (Exception e){
                    }
                }
                if(input!=null){
                    try{
                        input.close();
                    }catch (Exception e){
                    }
                }
            }
        }
    }
}

4、socket客户端(动态代理)

package com.guor.rpc.client;

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;

//通过socket + 动态代理 + 反射远程调用接口中的方法, 连接池, 并发
public class Client {
    //获取代表服务端接口的动态代理对象(HelloService)
    //serviceInterface:请求的接口名
    //add:待请求服务端的ip:port
    public static <T> T getRemoteProxyObj(Class serviceInterface, InetSocketAddress addr){
        System.out.println("Client start...");
        /*
            newProxyInstance(a, b, c);
            a:类加载器:需要代理哪个类,就要获取哪个类的类加载器
            b:需要代理的对象,具备哪些方法 --> 接口
            java中单继承、多实现
         */
        return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[]{serviceInterface}, new InvocationHandler() {
            /**
             *
             * @param proxy 代理的对象
             * @param method 哪个方法 sayHello()
             * @param args 参数列表
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) {
                ObjectOutputStream output = null;
                ObjectInputStream input = null;
                try {
                    //客户端向服务端发送请求:请求某一个具体的接口
                    Socket socket = new Socket();
                    socket.connect(new InetSocketAddress(9999));
                    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());
                    System.out.println("接收服务端处理后的返回值"+input.readObject());
                    return input.read();
                }catch (Exception e) {
                    System.out.println("invoke exception"+e);
                    return null;
                }finally {
                    if(input!=null){
                        try{
                            input.close();
                        }catch (Exception e){
                        }
                    }
                    if(output!=null) {
                        try {
                            output.close();
                        } catch (Exception e) {
                        }
                    }
                }
            }
        });
    }
}

5、测试类

package com.guor.rpc.test;

import com.guor.rpc.server.HelloService;
import com.guor.rpc.server.HelloServiceImpl;
import com.guor.rpc.server.ServerCenter;
import com.guor.rpc.server.ServerCenterImpl;

public class RPCServerTest {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //服务中心
                ServerCenter serverCenter = new ServerCenterImpl(9999);
                //将Hello接口和实现类注册到服务中心
                serverCenter.register(HelloService.class, HelloServiceImpl.class);
                serverCenter.start();
            }
        }).start();
    }
}
package com.guor.rpc.test;

import com.guor.rpc.client.Client;
import com.guor.rpc.server.HelloService;

import java.net.InetSocketAddress;

/**
 * 1、客户端通过socket请求服务端,并且通过字符串形式,将需要的接口发送给服务端(通过动态代理发送接口名、方法名)
 * 2、服务端将可以提供的接口注册到服务中心(通过map保存,key为接口的名字,value为接口的实现类)
 * 3、服务端接收到客户端的请求后,通过请求的接口名在服务中心的map中寻找对应的接口实现类
 * 找到之后,解析刚才客户端发过来的接口名、方法名,解析完毕后,通过反射技术将该方法执行,执行完毕后,再讲该方法的返回值返回给客户端
 */
public class RPCClientTest {
    public static void main(String[] args) throws Exception {
        HelloService service = Client.getRemoteProxyObj(Class.forName("com.guor.rpc.server.HelloService"), new InetSocketAddress("127.0.0.1", 9999));
        service.sayHello("素小暖");
    }
}

6、控制台输出

往期精彩内容:

Java知识体系总结(2021版)

Java多线程基础知识总结(绝对经典)

超详细的springBoot学习笔记

常见数据结构与算法整理总结

Java设计模式:23种设计模式全面解析(超级详细)

Java面试题总结(附答案)

猜你喜欢

转载自blog.csdn.net/guorui_java/article/details/114549239