從零手寫 RPC-version1

一、 前置知識

1. 反射

獲取字節碼的三種方式

  • Class.forName("全類名") (全類名,即包名+類名)
  • 類名.class
  • 對象.getClass() (任意對象都可調用,因為該方法來自Object類)

獲取成員方法

Method getMethod(String name,Class<?>...parameterTypes)

參數為:方法名,參數類型<可變>

執行成員方法

Object invoke(Object obj, Object ... args)

參數 1:哪個對象來調用該方法

參數 2 :傳入的實參

2. 動態代理

具體代碼實現參考下方的"代碼實現"部分

動態代理實現流程:

  1. 創建一個類來實現InvocationHandler接口(重寫invoke方法)
  2. 調用Proxy.newProxyInstance()來創建代理類(需要將第一步創建的類作為參數傳進來)
  3. 通過代理類來調用方法

二、上一個版本中的問題&解決思路

**問題 1:**服務端當前僅支持一個服務,當提供多個服務時,客戶端如何指定要調用哪個服務?(當前客戶端發送請求數據時,而只會傻乎乎地發送數據,而無法指定具體調用哪個接口)

當服務端提供多個服務時,客戶端需要指定調用的服務名稱。

創建一個請求對象類Request,其中包含的成員屬性有:接口名稱、要調用的方法名稱、傳遞的參數數據,服務端利用這些信息,來使用反射調用相應的服務

**問題 2:**當前服務端中的方法返回類型是固定的,但是當有不同的方法時,返回數據類型可能不同,如果客戶端要處理這些不同類型的數據,就需要提前知道服務端返回的數據類型。但這顯然會違背解耦的原則,降低靈活性,而且不便于后續維護和擴展

引入統一的響應格式——將返回數據封裝到一個公共類型中

創建一個響應對象類Response,其中包含的成員屬性有:狀態碼、狀態描述、響應數據(這種思想在 javaweb 開發也經常使用)

**問題 3:**在上個版本中,客戶端和目標主機建立連接時,采用了硬編碼,不夠優雅

**問題 4:**如果仍然采用上個版本的客戶端代碼,那么代碼耦合性較高(建立連接、發送請求的代碼、接收響應、處理響應結果都寫在了一起)

針對問題 3、4,將這些問題抽象了出來,建立一個 IOClient 類,專門建立連接、發送請求、接收響應。

三、本版本目標

  1. 將請求、響應的數據各自封裝到一個公共類中,這樣在請求和響應時就能進行統一,便于后續代碼的書寫和維護
  2. 服務端采用循環+BIO的形式,當接收到請求對象時,利用反射調用對應方法,并將執行結果發送給客戶端
  3. 將建立連接、發送數據、接收數據的過程封裝到專用組件中(會將請求參數封裝到一個請求對象中(包含方法、參數類型、參數列表等))
  4. 利用動態代理,攔截所有對接口方法的調用,將其轉換為 RPC 請求

四、代碼實現

服務端

server/Server
package com.chanlee.crpc.v1.server;import com.chanlee.crpc.v1.common.RpcRequestDTO;
import com.chanlee.crpc.v1.common.RpcResponseDTO;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
/*** 服務端代碼*/
public class Server {private static final int SERVER_PORT = 8005;public static void main(String[] args) {UserServiceImpl userService = new UserServiceImpl();try (ServerSocket serverSocket = new ServerSocket(SERVER_PORT)){System.out.println("服務器已啟動...");while(true){Socket socket = serverSocket.accept();//接收到連接請求后,啟動一個新線程去處理任務new Thread(() -> {try {// 獲取輸入、輸出流ObjectOutputStream objectOutput = new ObjectOutputStream(socket.getOutputStream());ObjectInputStream objectInput = new ObjectInputStream(socket.getInputStream());//讀取接收到的請求RpcRequestDTO request = (RpcRequestDTO) objectInput.readObject();//利用反射調用對應方法Method method = userService.getClass().getMethod(request.getMethod(), request.getParamsTypes());Object invoke = method.invoke(userService, request.getParams());//將調用結果進行封裝objectOutput.writeObject(RpcResponseDTO.success(invoke));//及時傳遞消息objectOutput.flush();} catch (Exception e) {e.printStackTrace();}}).start();}} catch (IOException e) {System.out.println("服務器啟動失敗...");}}
}
server/UserServiceImpl
package com.chanlee.crpc.v1.server;import com.chanlee.crpc.v1.common.User;
import com.chanlee.crpc.v1.service.UserService;import java.util.Random;
import java.util.UUID;/*** 服務端接口實現類*/
public class UserServiceImpl implements UserService {public User getUserById(int id) {System.out.println("客戶端調用id 為 " + id + " 的用戶");Random random = new Random();User user = User.builder().id(id).realName(UUID.randomUUID().toString()).age(random.nextInt(50)).build();return user;}public Integer insertUser(User user) {System.out.println("插入用戶成功:" + user);return 1;}
}

客戶端

client/Client
package com.chanlee.crpc.v1.client;import com.chanlee.crpc.v1.common.User;
import com.chanlee.crpc.v1.service.UserService;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;/*** 客戶端主代碼*/
public class Client{public static void main(String[] args){ClientProxy clientProxy = new ClientProxy("127.0.0.1", 8005);UserService proxy = clientProxy.getProxy(UserService.class);//調用方法 1User user = proxy.getUserById(1);System.out.println("對應的user為:" + user);//調用方法 2User codingBoy = User.builder().age(25).id(32).realName("coding boy").build();Integer i = proxy.insertUser(codingBoy);System.out.println("向服務端插入的 user的Id為:" + i);}
}
client/Proxy
package com.chanlee.crpc.v1.client;import com.chanlee.crpc.v1.common.RpcRequestDTO;
import com.chanlee.crpc.v1.common.RpcResponseDTO;
import lombok.AllArgsConstructor;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;import static com.chanlee.crpc.v1.client.IOClient.sendRequest;/*** 客戶端代理類*/
@AllArgsConstructor
public class ClientProxy implements InvocationHandler {/*** 服務端 IP*/private String host;/*** 服務端端口號*/private int port;public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//構建request請求RpcRequestDTO request = RpcRequestDTO.builder().interfaceName(method.getDeclaringClass().getName()).method(method.getName()).paramsTypes(method.getParameterTypes()).params(args).build();//發送請求并獲取響應RpcResponseDTO<Object> response = sendRequest(host, port, request);//返回結果數據return response.getData();}public <T> T getProxy(Class<T> tClass){Object o = Proxy.newProxyInstance(tClass.getClassLoader(),new Class[]{tClass},this);return (T)o;}
}
client/IOClient
package com.chanlee.crpc.v1.client;import com.chanlee.crpc.v1.common.RpcRequestDTO;
import com.chanlee.crpc.v1.common.RpcResponseDTO;
import lombok.extern.slf4j.Slf4j;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.Socket;/*** 客戶端 IO 組件*/
@Slf4j
public class IOClient implements Serializable {public static <T> RpcResponseDTO<T> sendRequest(String host, int port, RpcRequestDTO request){//和服務器建立連接try {Socket socket = new Socket(host, port);// 獲取輸入流和輸出流ObjectOutputStream objectOutput = new ObjectOutputStream(socket.getOutputStream());ObjectInputStream objectInput = new ObjectInputStream(socket.getInputStream());//發送請求objectOutput.writeObject(request);objectOutput.flush();//接收結果RpcResponseDTO<T> response = (RpcResponseDTO<T>) objectInput.readObject();//關閉連接socket.close();//返回結果return response;} catch (IOException e) {log.error("和服務器建立連接失敗: {}", e);throw new RuntimeException(e);} catch (ClassNotFoundException e) {log.error("接收結果失敗: {}", e);throw new RuntimeException(e);}}
}

服務層

service/UserService
package com.chanlee.crpc.v1.service;import com.chanlee.crpc.v1.common.User;/*** 服務端接口*/
public interface UserService {/*** 根據id獲取用戶信息* @param id* @return*/User getUserById(int id);/*** 插入用戶信息* @param user* @return*/Integer insertUser(User user);
}

公共類

convention/BaseErrorCode
package com.chanlee.crpc.v1.common.convention;/*** 基礎錯誤碼*/
public enum BaseErrorCode implements ErrorCode {SERVER_ERROR("A000001", "服務端內部錯誤");private final String code;private final String message;BaseErrorCode(String code, String message) {this.code = code;this.message = message;}@Overridepublic String code() {return code;}@Overridepublic String message() {return message;}
}
convention/ErrorCode
package com.chanlee.crpc.v1.common.convention;/*** 錯誤碼接口*/
public interface ErrorCode {/*** 錯誤碼*/String code();/*** 錯誤信息*/String message();
}
commom/RpcRequestDTO
package com.chanlee.crpc.v1.common;import lombok.Builder;
import lombok.Data;import java.io.Serializable;/*** 請求對象體*/
@Builder
@Data
public class RpcRequestDTO implements Serializable {/*** 接口名*/private String interfaceName;/*** 方法名*/private String method;/*** 參數*/private Object[] params;/*** 參數類型*/private Class<?>[] paramsTypes;
}
Common/RpcRespDTO
package com.chanlee.crpc.v1.common;import com.chanlee.crpc.v1.common.convention.BaseErrorCode;
import lombok.Data;
import lombok.experimental.Accessors;import java.io.Serializable;/*** 響應對象體*/
@Data
@Accessors(chain = true)
public class RpcResponseDTO<T> implements Serializable {/*** 正確返回碼*/public static final String SUCCESS_CODE = "200";/*** 返回碼*/private String code;/*** 返回消息*/private String message;/*** 響應數據*/private T data;public static RpcResponseDTO<Void> success(){return new RpcResponseDTO<Void>().setCode(SUCCESS_CODE);}public static <T> RpcResponseDTO<T> success(T data){return new RpcResponseDTO<T>().setCode(SUCCESS_CODE).setData(data);}public static RpcResponseDTO<Void> failure(){return new RpcResponseDTO<Void>().setMessage(BaseErrorCode.SERVER_ERROR.code()).setCode(BaseErrorCode.SERVER_ERROR.message());}
}
common/User
package com.chanlee.crpc.v1.common;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;
/*** 用戶類*/
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {/*** 用戶id*/Integer id;/*** 用戶真實姓名*/String realName;/*** 用戶年齡*/Integer age;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/77904.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/77904.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/77904.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ARINC818協議(六)

上圖中&#xff0c;紅色虛線上面為我們常用的simple mode簡單模式&#xff0c;下面和上面的結合在一起&#xff0c;就形成了extended mode擴展模式。 ARINC818協議 container header容器頭 ancillary data輔助數據 視頻流 ADVB幀映射 FHCP傳輸協議 R_CTRL:路由控制routing ctr…

PyCharm 鏈接 Podman Desktop 的 podman-machine-default Linux 虛擬環境

#工作記錄 PyCharm Community 連接到Podman Desktop 的 podman-machine-default Linux 虛擬環境詳細步驟 1. 準備工作 確保我們已在 Windows 系統中正確安裝并啟動了 Podman Desktop。 我們將通過 Podman Desktop 提供的名為 podman-machine-default 的 Fedora Linux 41 WSL…

小白自學python第一天

學習python的第一天 一、常用的值類型&#xff08;先來粗略認識一下~&#xff09; 類型說明數字&#xff08;number&#xff09;包含整型&#xff08;int&#xff09;、浮點型&#xff08;float&#xff09;、復數&#xff08;complex&#xff09;、布爾&#xff08;boolean&…

初階數據結構--排序算法(全解析!!!)

排序 1. 排序的概念 排序&#xff1a;所謂排序,就是使一串記錄&#xff0c;按照其中的某個或某些些關鍵字的大小&#xff0c;遞增或遞減的排列起來的操作。 2. 常見的排序算法 3. 實現常見的排序算法 以下排序算法均是以排升序為示例。 3.1 插入排序 基本思想&#xff1a;…

Android studio開發——room功能實現用戶之間消息的發送

文章目錄 1. Flask-SocketIO 后端代碼后端代碼 2. Android Studio Java 客戶端代碼客戶端代碼 3. 代碼說明 SocketIO基礎 1. Flask-SocketIO 后端代碼 后端代碼 from flask import Flask, request from flask_socketio import SocketIO, emit import uuidapp Flask(__name_…

4.LinkedList的模擬實現:

LinkedList的底層是一個不帶頭的雙向鏈表。 不帶頭雙向鏈表中的每一個節點有三個域&#xff1a;值域&#xff0c;上一個節點的域&#xff0c;下一個節點的域。 不帶頭雙向鏈表的實現&#xff1a; public class Mylinkdelist{//定義一個內部類&#xff08;節點&#xff09;stat…

Sentinel數據S2_SR_HARMONIZED連續云掩膜+中位數合成

在GEE中實現時&#xff0c;發現簡單的QA60是無法去云的&#xff0c;最近S2地表反射率數據集又進行了更新&#xff0c;原有的屬性集也進行了變化&#xff0c;現在的SR數據集名稱是“S2_SR_HARMONIZED”。那么&#xff1a; 要想得到研究區無云的圖像&#xff0c;可以參考執行以下…

理解計算機系統_網絡編程(1)

前言 以<深入理解計算機系統>(以下稱“本書”)內容為基礎&#xff0c;對程序的整個過程進行梳理。本書內容對整個計算機系統做了系統性導引,每部分內容都是單獨的一門課.學習深度根據自己需要來定 引入 網絡是計算機科學中非常重要的部分,筆者過去看過相關的內…

【2025】Datawhale AI春訓營-RNA結構預測(AI+創新藥)-Task2筆記

【2025】Datawhale AI春訓營-RNA結構預測&#xff08;AI創新藥&#xff09;-Task2筆記 本文對Task2提供的進階代碼進行理解。 任務描述 Task2的任務仍然是基于給定的RNA三維骨架結構&#xff0c;生成一個或多個RNA序列&#xff0c;使得這些序列能夠折疊并盡可能接近給定的目…

vim 命令復習

命令模式下的命令及快捷鍵 # dd刪除光所在行的內容 # ndd從光標所在行開始向下刪除n行 # yy復制光標所在行的內容 # nyy復制光標所在行向下n行的內容 # p將復制的內容粘貼到光標所在行以下&#xff08;小寫&#xff09; # P將復制的內容粘貼到光標所在行以上&#xff08;大寫&…

哪些心電圖表現無緣事業編體檢呢?

根據《公務員錄用體檢通用標準》心血管系統條款及事業單位體檢實施細則&#xff0c;心電圖不合格主要涉及以下類型及處置方案&#xff1a; 一、心律失常類 早搏&#xff1a;包括房性早搏、室性早搏和交界性早搏。如果每分鐘早搏次數較多&#xff08;如超過5次&#xff09;&…

Linux學習——UDP

編程的整體框架 bind&#xff1a;綁定服務器&#xff1a;TCP地址和端口號 receivefrom()&#xff1a;阻塞等待客戶端數據 sendto():指定服務器的IP地址和端口號&#xff0c;要發送的數據 無連接盡力傳輸&#xff0c;UDP:是不可靠傳輸 實時的音視頻傳輸&#x…

ReAct Agent 實戰:基于DeepSeek從0到1實現大模型Agent的探索模式

寫在前面:動態思考,邊想邊做 大型語言模型(LLM)的崛起開啟了通用人工智能(AGI)的無限遐想。但要讓 LLM 從一個被動的“文本生成器”轉變為能夠主動解決問題、與環境交互的智能體(Agent),我們需要賦予它思考、行動和學習的能力。ReAct (Reason + Act) 框架正是實現這一…

從物理到預測:數據驅動的深度學習的結構化探索及AI推理

在當今科學探索的時代&#xff0c;理解的前沿不再僅僅存在于我們書寫的方程式中&#xff0c;也存在于我們收集的數據和構建的模型中。在物理學和機器學習的交匯處&#xff0c;一個快速發展的領域正在興起&#xff0c;它不僅觀察宇宙&#xff0c;更是在學習宇宙。 AI推理 我們…

結合地理數據處理

CSV 文件不僅可以存儲表格數據&#xff0c;還可以與地理空間數據結合&#xff0c;實現更強大的地理處理功能。例如&#xff0c;你可以將 CSV 文件中的坐標數據轉換為點要素類&#xff0c;然后進行空間分析。 示例&#xff1a;將 CSV 文件中的坐標數據轉換為點要素類 假設我們有…

SpringBoot中6種自定義starter開發方法

在SpringBoot生態中,starter是一種特殊的依賴,它能夠自動裝配相關組件,簡化項目配置。 自定義starter的核心價值在于: ? 封裝復雜的配置邏輯,實現開箱即用 ? 統一技術組件的使用規范,避免"輪子"泛濫 ? 提高開發效率,減少重復代碼 方法一:基礎配置類方式 …

滾珠導軌松動會導致哪些影響?

直線導軌用于高精度或快速直線往復運動場所&#xff0c;且能夠擔負一定的扭矩&#xff0c;在高負載的情況下實現高精度的直線運動。它主要由導軌和滑塊組成&#xff0c;其中導軌作為固定元件&#xff0c;滑塊則在其上進行往復直線運動。但是滾珠導軌松動會導致哪些影響&#xf…

從零開始搭建Django博客②--Django的服務器內容搭建

本文主要在Ubuntu環境上搭建&#xff0c;為便于研究理解&#xff0c;采用SSH連接在虛擬機里的ubuntu-24.04.2-desktop系統搭建&#xff0c;當涉及一些文件操作部分便于通過桌面化進行理解&#xff0c;通過Nginx代理綁定域名&#xff0c;對外發布。 此為從零開始搭建Django博客…

ZLMediaKit支持JT1078實時音視頻

ZLMediaKit 對 JT1078 實時音視頻協議的支持主要通過其擴展版本或與其他中間件結合實現。以下是基于搜索結果的綜合分析&#xff1a; 一、ZLMediaKit 原生支持能力 開源版本的基礎支持 ZLMediaKit 開源版本本身未直接集成 JT1078 協議解析模塊&#xff0c;但可通過 RTP 推流功能…

Java隊列(Queue)核心操作與最佳實踐:深入解析與面試指南

文章目錄 概述一、Java隊列核心實現類對比1. LinkedList2. ArrayDeque3. PriorityQueue 二、核心操作API與時間復雜度三、經典使用場景與最佳實踐場景1&#xff1a;BFS層序遍歷&#xff08;樹/圖&#xff09;場景2&#xff1a;滑動窗口最大值&#xff08;單調隊列&#xff09; …