从零手写 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.mzph.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; …