Realize RPC remote method calls through socket, multithreading, dynamic proxy, reflection

1. Concept combing

1. What is Socket?

Socket is the middleware abstraction layer for communication between the application layer and the TCP/IP protocol suite. It is a set of interfaces. In the design mode, Socket is actually a facade mode, which hides the complex TCP/IP protocol family behind the Socket interface. For users, a set of simple interfaces is everything, allowing Socket to organize data to conform to the specified protocol.

Detailed Socket technology

2. What is a dynamic agent?

At present, the implementation of java dynamic proxy is divided into two types

1. JDK-based dynamic proxy

2. Dynamic agent based on CGILB

The use of dynamic agents in business is generally to add preprocessing or subsequent operations to the methods that need to be implemented, but does not interfere with the normal business of the implementation class, and separates some basic business from the main business logic. The Spring AOP principle that we generally know is based on dynamic proxy implementation.

[Java knowledge points detailed 2] dynamic agent

3. What is reflection?

The Java reflection mechanism is in the running state, for any class, you can know all the properties and methods of this class; for any object, you can call any of its methods and properties; this kind of dynamic acquisition of information and dynamic invocation of object methods The function is called the reflection mechanism of java language.

[Java knowledge points detailed 8] reflection

4. What is RPC?

RPC is the abbreviation of remote procedure call, which is widely used in large-scale distributed applications. Its function is to help the vertical division of the system and make the system easier to expand. There are many RPC frameworks in Java, each with its own characteristics, and the widely used ones are RMI, Hessian, Dubbo, etc. Another feature of RPC is its ability to cross languages.

Comparison of RPC service and HTTP service

Two, simulate RPC remote method calls

1. Ideas

  1. The client requests the server through the socket, and sends the required interface to the server in the form of a string (the interface name and method name are sent through the dynamic proxy).
  2. The server registers the available interfaces to the service center (stored by map, the key is the name of the interface, and the value is the implementation class of the interface).
  3. After the server receives the client's request, it searches for the corresponding interface implementation class in the map of the service center through the requested interface name. After finding the corresponding interface implementation class, it parses the interface name and method name sent by the client. The method is executed, and after the execution is complete, the return value of the method is returned to the client.

2. Analyze the sketch

​Three, code implementation

1. Interface

package com.guor.rpc.server;

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

2. Interface implementation class 

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 server (multithreading, reflection)

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 client (dynamic proxy)

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. Testing

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. Console output

 

Highlights from previous issues:

Summary of Java knowledge system (2021 version)

Summary of basic knowledge of Java multithreading (absolutely classic)

Super detailed springBoot study notes

Summary of common data structures and algorithms

Java design patterns: a comprehensive analysis of 23 design patterns (super detailed)

Summary of Java interview questions (with answers)

Guess you like

Origin blog.csdn.net/guorui_java/article/details/114549239