RPC簡單實現
RPC是什麼?
- RPC(Remote Procedure Call)遠程過程調用,所謂遠程調用,就是一個結點需要通過網絡通信來進行調用另一個結點。
RPC服務端如何搭建?
-
首先,你要客戶端能訪問到,得有一個ServerSocket監聽socket連接,通過socket進行數據交互。
-
當客戶端連接請求過來了,你要處理socket傳輸過來的數據,這個數據要表達的是我要調用你提供服務的哪個方法,有哪些參數。爲了方便,我們在通信雙方創建一個類用於數據傳輸。
//省去構造方法和get、set //要網絡傳輸就要序列化與反序列化 public class RpcRequest implements Serializable { //序列UID,省去可能出現java.io.InvalidClassException private static final long serialVersionUID = 1631280650588763177L; private String methodName; private Object[] parameters; }
-
創建一個服務接口和實現類。
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"; } }
-
有了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; } }
-
服務發佈。監聽連接,有連接時開啓一個線程去執行。
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地址變了,所有客戶端的調用也需要跟隨着改變。(通過註冊中心統一管理,提供服務名即可)