基於Socket簡單實現RPC

RPC簡單實現

RPC是什麼?

  • RPC(Remote Procedure Call)遠程過程調用,所謂遠程調用,就是一個結點需要通過網絡通信來進行調用另一個結點。

RPC服務端如何搭建?

  1. 首先,你要客戶端能訪問到,得有一個ServerSocket監聽socket連接,通過socket進行數據交互。

  2. 當客戶端連接請求過來了,你要處理socket傳輸過來的數據,這個數據要表達的是我要調用你提供服務的哪個方法,有哪些參數。爲了方便,我們在通信雙方創建一個類用於數據傳輸。

    //省去構造方法和get、set
    //要網絡傳輸就要序列化與反序列化
    public class RpcRequest implements Serializable {
        //序列UID,省去可能出現java.io.InvalidClassException
        private static final long serialVersionUID = 1631280650588763177L;
        private String methodName;
        private Object[] parameters;
    }
    
  3. 創建一個服務接口和實現類。

    public interface IUserService {
        String login(User user);
    }
    
    public class UserServiceImpl implements IUserService {
        @Override
        public String login(User user) {
            System.out.println("從數據庫查詢用戶:"+user.getName());
            return "success";
        }
    }
    
  4. 有了socket對象就能獲取到數據,然後通過反射獲取相應方法並執行,通過socket返回客戶端執行結果。創建一個類實現Runnable接口,有兩個參數需要傳入,一個是連接的socket對象,一個是暴露服務接口的實現類對象。

    public class ProcessorHandler implements Runnable {
        private Socket socket;
        private Object service;   //服務接口的實現類對象,如UserServiceImpl對象
    
        public ProcessorHandler(Socket socket, Object service) {
            this.socket = socket;
            this.service = service;
        }
    
        @Override
        public void run() {
            System.out.println("開始處理....");
            ObjectInputStream inputStream = null;
            ObjectOutputStream outputStream = null;
            try {
                //獲取客戶端傳過來的流
                inputStream = new ObjectInputStream(socket.getInputStream());
                //反序列化
                RpcRequest rpcRequest = (RpcRequest) inputStream.readObject();
                //rpcRequest中包括要執行的方法和方法的參數
                //執行客戶端請求要執行的方法並返回結果
                Object invoke = invoke(rpcRequest);
                
                //通過socket輸出流返回給客戶端執行方法後的返回結果
                outputStream = new ObjectOutputStream(socket.getOutputStream());
                outputStream.writeObject(invoke);
                outputStream.flush();
                System.out.println("執行結束");
    
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }finally {
    			if(inputStream != null){
                    inputStream.close();
                }
                if(outputStream != null){
                    outputStream.close();
                }
            }
        }
    
        //通過反射執行方法
        private Object invoke(RpcRequest request){
            //獲取參數並封裝爲Object數組
            Object[] args = request.getParameters();
            //獲取參數的class類數組用於反射時找到對應的方法
            Class<?>[] types = new Class[args.length];
            for (int i = 0; i < args.length; i++) {
                types[i] = args[i].getClass();
            }
            try {
                //反射獲取方法並執行方法,返回結果
                Method method = service.getClass().getMethod(request.getMethodName(), types);
                Object result = method.invoke(service, args);
                return result;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    
  5. 服務發佈。監聽連接,有連接時開啓一個線程去執行。

    public class RpcServiceProxy {
    
        private ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0,
                TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(512));
    
        public void publisher(Object service, int port){
            ServerSocket serverSocket = null;
            try {
                serverSocket = new ServerSocket(port);
                while(true){
                    Socket socket = serverSocket.accept();  //接收連接請求
                    executorService.execute(new ProcessorHandler(socket, service));
                }
            }catch (Exception e){
                throw new RuntimeException("服務發佈失敗");
            }finally {
                if(serverSocket != null){
                    try {
                        serverSocket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        
        //發佈服務
        public static void main( String[] args )
        {
            IUserService userService = new UserServiceImpl();
            RpcServiceProxy rpcServiceProxy = new RpcServiceProxy();
            rpcServiceProxy.publisher(userService, 8080); //發佈服務
        }
    }
    
    

搭建RPC客戶端去調用

public class App
{
    public static void main( String[] args ) throws Exception {
        //建立連接
        Socket socket = new Socket("localhost", 8080);
		//封裝傳輸數據
        RpcRequest request = new RpcRequest("login", new Object[]{new User("maolaoke")});
        //向服務端發送數據
        ObjectOutputStream objectOutputStream = new 
                    ObjectOutputStream(socket.getOutputStream());
        objectOutputStream.writeObject(request);
        objectOutputStream.flush();

        //接受服務端返回的數據
        ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
        Object result = inputStream.readObject();
        System.out.println(result);
        
        //關閉輸入輸出流
        inputStream.close();
        objectOutputStream.close();
    }
}

以上就實現了一個簡單的遠程過程調用。




存在的問題?

客戶端正常不會這麼去寫,而是基於動態代理實現。

一個服務如果ip地址變了,所有客戶端的調用也需要跟隨着改變。(通過註冊中心統一管理,提供服務名即可)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章