Handwritten RPC framework

Posted by Spades on Fri, 29 Oct 2021 16:33:21 +0200

Handwritten RPC framework (I)

RPC(Remote Procedure Call), that is, remote procedure call, is mainly used in distributed applications. Services are deployed on different machines to call the content in the remote server through the RPC framework. Generally, the RPC framework adopts the client / Provider mode, and its main processes are:

  • Client call function
  • Serialize the call information (called classes, methods, method parameters, etc.), and send the data packet obtained from the serialized information to the Provider through socket and other means
  • The Provider deserializes the call information from the packet. The Provider executes the request and serializes the returned results
  • Send the serialized packet to the Consumer, and the Consumer deserializes the received data

This article mainly refers to articles How to implement a simple RPC , this article only implements the remote call to the Calculator class, which is lack of generality. Later, it will gradually realize the functions of service registration, load balancing and dynamic proxy, and gradually improve the RPC framework.

This paper implements an RPC framework of calculator class. The client returns the calculation results by calling the program and the server.

  • Calculator class for Provider

    Interface

    public interface Calculator {
        int add(int a,int b);
        int sub(int a,int b);
        int mul(int a,int b);
        int div(int a,int b);
    }
    

    Implementation of calculator class

    public class CalculatorImpl implements Calculator{
        @Override
        public int add(int a, int b) {
            return a+b;
        }
    
        @Override
        public int sub(int a, int b) {
            return a-b;
        }
    
        @Override
        public int mul(int a, int b) {
            return a*b;
        }
    
        @Override
        public int div(int a, int b) {
            if(b==0){
                throw new ArithmeticException();
            }
            else{
                return a/b;
            }
        }
    }
    
  • When defining a remote call, the message body sent by the client to the server needs to be serialized and delivered, so it needs to implement Serializable

    package request;
    
    import java.io.Serializable;
    
    public class CalculateRpcRequest implements Serializable {
        private static final long serialVersionUID = 7503710091945320739L;
        private int a;
        private int b;
        private String name;
    
        public int getA() {
            return a;
        }
    
        public void setA(int a) {
            this.a = a;
        }
    
        public int getB() {
            return b;
        }
    
        public void setB(int b) {
            this.b = b;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "CalculateRpcRequest{" +
                    "a=" + a +
                    ", b=" + b +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    
  • Define the logic of the Consumer when calling remotely

    public class CalculatorRemoteImpl implements Calculator {
    	//Override methods in Calculator
        @Override
        public int add(int a, int b) {
            //Address of remote call
            String address="127.0.0.1";
            try{
                //Establish socket connection with remote service
                Socket socket=new Socket(address,PORT);
                //Message body serialization
                CalculateRpcRequest calculateRpcRequest=generateRequest(a,b);
                //Initialize the serialization stream of the object, convert the object into byte data, and send it through socket
                ObjectOutputStream objectOutputStream=new ObjectOutputStream(socket.getOutputStream());
                //Write the message body of the call
                objectOutputStream.writeObject(calculateRpcRequest);
    			//Get return data
                ObjectInputStream objectInputStream=new ObjectInputStream(socket.getInputStream());
                Object response =objectInputStream.readObject();
                System.out.println(response);
                if(response instanceof Integer){
                    return (Integer) response;
                }
                else{
                    throw new InternalError();
                }
            } catch (Exception e) {
                System.out.println("ERROR!");
                throw new InternalError();
            }
        }
    
    	//Message body instantiation
        public CalculateRpcRequest generateRequest(int a,int b){
            CalculateRpcRequest calculateRpcRequest=new CalculateRpcRequest();
            calculateRpcRequest.setA(a);
            calculateRpcRequest.setB(b);
            calculateRpcRequest.setName("add");
            return calculateRpcRequest;
        }
        public static final int PORT = 8080;
    }
    
  • Define the logic of the Provider when calling remotely

    public class Provider {
        private final Calculator calculator = new CalculatorImpl();
    
        public static void main(String[] args) throws IOException {
            new Provider().run();
        }
    
        public void run() throws IOException {
            //Listen for data on port 8080
            ServerSocket listener = new ServerSocket(8080);
            try {
                Socket socket = listener.accept();
                while (true) {
                    try {
                        //Deserialize data
                        ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                        Object object = objectInputStream.readObject();
                        int res = 0;
                        if (object instanceof CalculateRpcRequest) {
                            CalculateRpcRequest calculateRpcRequest = (CalculateRpcRequest) object;
                            //Judgment execution method
                            if ("add".equals(calculateRpcRequest.getName())) {
                                //Execute the method
                                res = calculator.add(calculateRpcRequest.getA(), calculateRpcRequest.getB());
                            } else {
                                throw new UnsupportedOperationException();
                            }
                        }
                        //Return data serialization
                        ObjectOutputStream objectOutputStream=new ObjectOutputStream(socket.getOutputStream());
                        objectOutputStream.writeObject(new Integer(res));
                    } catch (Exception e) {
                        System.out.println("Reverse sequence failed!");
                    } finally {
                        socket.close();
                    }
                }
            } finally {
                listener.close();
            }
        }
    }
    
  • Remote call via RPC

    public class Consumer {
    
        public static void main(String[] args) {
            //Instantiate and initiate rpc request
    		CalculatorRemoteImpl cal=new CalculatorRemoteImpl();
            //Remote call
    		int res=cal.add(1000,210);
    		System.out.println("res= "+res);
    
        }
    }
    

This time, I am only familiar with the basic process and principle of RPC in the previous article, and it is difficult to expand the project through the direct proxy Calculator class in the framework, so it can not be regarded as a real RPC framework. After that, the RPC framework will be improved by implementing dynamic proxy through reflection in Java.

Project address: https://github.com/iven98/irpc.git (v1.0)

Topics: Java rpc Dynamic Proxy