手撕RPC——实现简单的RPC调用

手撕RPC——实现简单的RPC调用

  • 一、场景设计
  • 二、设计思路
    • 2.1 客户端的设计
    • 2.2 服务端的设计
    • 2.3 通信设计
  • 三、代码实现
    • 3.1 定义用户信息
    • 3.2 用户服务接口
    • 3.3 用户服务接口实现
    • 3.4 定义消息格式
    • 3.5 实现动态代理类
    • 3.6 封装信息传输类
    • 3.7 定义服务端Server接口
    • 3.8 实现RpcServer接口
    • 3.9 实现WorkThread类
    • 3.10 实现本地服务存放器
    • 3.11 客户端主程序
    • 3.12 服务端主程序

一、场景设计

现在A,B位于不同的服务器上,但现在A想调用B的某个方法,如何实现呢?

服务端B
有一个用户表

  1. UserService 里有一个功能:getUserByUserId(Integer id)
  2. UserServiceImpl 实现了UserService接口和方法

客户端A
调用getUserByUserId方法, 内部传一个Id给服务端,服务端查询到User对象返回给客户端

如何实现以上调用过程呢?

二、设计思路

主要考虑客户端、服务端、以及双方如何通信才能实现此功能

2.1 客户端的设计

  1. 调用getUserByUserId方法时,内部将调用信息处理后发送给服务端B,告诉B我要获取User
  2. 外部调用方法,内部进行其它的处理——这种场景我们可以使用动态代理的方式,改写原本方法的处理逻辑

2.2 服务端的设计

  1. 监听到A的请求后,接收A的调用信息,并根据信息得到A想调用的服务与方法
  2. 根据信息找到对应的服务,进行调用后将结果发送回给A

2.3 通信设计

  1. 使用Java的socket网络编程进行通信
  2. 为了方便A ,B之间 对接收的消息进行处理,我们需要将请求信息和返回信息封装成统一的消息格式

三、代码实现

在此部分我们将理论转化为代码,分别实现客户端和服务端。

项目目录结构
在这里插入图片描述

3.1 定义用户信息

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {// 客户端和服务端共有的private Integer id;private String userName;private Boolean sex;
}

3.2 用户服务接口

public interface UserService {// 客户端通过这个接口调用服务端的实现类User getUserByUserId(Integer id);//新增一个功能Integer insertUserId(User user);
}

3.3 用户服务接口实现

public class UserServiceImpl implements UserService {@Overridepublic User getUserByUserId(Integer id) {System.out.println("客户端查询了"+id+"的用户");// 模拟从数据库中取用户的行为Random random = new Random();User user = User.builder().userName(UUID.randomUUID().toString()).id(id).sex(random.nextBoolean()).build();return user;}@Overridepublic Integer insertUserId(User user) {System.out.println("插入数据成功"+user.getUserName());return user.getId();}
}

3.4 定义消息格式

//定义请求信息格式RpcRequest
@Data
@Builder
public class RpcRequest implements Serializable {//服务类名,客户端只知道接口private String interfaceName;//调用的方法名private String methodName;//参数列表private Object[] params;//参数类型private Class<?>[] paramsType;
}//定义返回信息格式RpcResponse(类似http格式)
@Data
@Builder
public class RpcResponse implements Serializable {//状态码private int code;//状态信息private String message;//具体数据private Object data;//构造成功信息public static RpcResponse sussess(Object data){return RpcResponse.builder().code(200).data(data).build();}//构造失败信息public static RpcResponse fail(){return RpcResponse.builder().code(500).message("服务器发生错误").build();}
}

3.5 实现动态代理类

@AllArgsConstructor
public class ClientProxy implements InvocationHandler {//传入参数service接口的class对象,反射封装成一个requestprivate String host;private int port;//jdk动态代理,每一次代理对象调用方法,都会经过此方法增强(反射获取request对象,socket发送到服务端)@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//构建requestRpcRequest request=RpcRequest.builder().interfaceName(method.getDeclaringClass().getName()).methodName(method.getName()).params(args).paramsType(method.getParameterTypes()).build();//IOClient.sendRequest 和服务端进行数据传输RpcResponse response= IOClient.sendRequest(host,port,request);return response.getData();}public <T>T getProxy(Class<T> clazz){Object o = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, this);return (T)o;}
}

3.6 封装信息传输类

public class IOClient {//这里负责底层与服务端的通信,发送request,返回responsepublic static RpcResponse sendRequest(String host, int port, RpcRequest request){try {Socket socket=new Socket(host, port);ObjectOutputStream oos=new ObjectOutputStream(socket.getOutputStream());ObjectInputStream ois=new ObjectInputStream(socket.getInputStream());oos.writeObject(request);oos.flush();RpcResponse response=(RpcResponse) ois.readObject();return response;} catch (IOException | ClassNotFoundException e) {e.printStackTrace();return null;}}
}

3.7 定义服务端Server接口

public interface RpcServer {//开启监听void start(int port);void stop();
}

3.8 实现RpcServer接口

@AllArgsConstructor
public class SimpleRPCRPCServer implements RpcServer {private ServiceProvider serviceProvide;@Overridepublic void start(int port) {try {ServerSocket serverSocket=new ServerSocket(port);System.out.println("服务器启动了");while (true) {//如果没有连接,会堵塞在这里Socket socket = serverSocket.accept();//有连接,创建一个新的线程执行处理new Thread(new WorkThread(socket,serviceProvide)).start();}} catch (IOException e) {e.printStackTrace();}}@Overridepublic void stop() {}
}

3.9 实现WorkThread类

WorkThread类负责启动线程和客户端进行数据传输,WorkThread类中的getResponse方法负责解析收到的request信息,寻找服务进行调用并返回结果。

@AllArgsConstructor
public class WorkThread implements Runnable{private Socket socket;private ServiceProvider serviceProvide;@Overridepublic void run() {try {ObjectOutputStream oos=new ObjectOutputStream(socket.getOutputStream());ObjectInputStream ois=new ObjectInputStream(socket.getInputStream());//读取客户端传过来的requestRpcRequest rpcRequest = (RpcRequest) ois.readObject();//反射调用服务方法获取返回值RpcResponse rpcResponse=getResponse(rpcRequest);//向客户端写入responseoos.writeObject(rpcResponse);oos.flush();} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}private RpcResponse getResponse(RpcRequest rpcRequest){//得到服务名String interfaceName=rpcRequest.getInterfaceName();//得到服务端相应服务实现类Object service = serviceProvide.getService(interfaceName);//反射调用方法Method method=null;try {method= service.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParamsType());Object invoke=method.invoke(service,rpcRequest.getParams());return RpcResponse.sussess(invoke);} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {e.printStackTrace();System.out.println("方法执行错误");return RpcResponse.fail();}}
}

3.10 实现本地服务存放器

因为一个服务器会有多个服务,所以需要设置一个本地服务存放器serviceProvider存放服务,在接收到服务端的request信息之后,我们在本地服务存放器找到需要的服务,通过反射调用方法,得到结果并返回

//本地服务存放器
public class ServiceProvider {//集合中存放服务的实例private Map<String,Object> interfaceProvider;public ServiceProvider(){this.interfaceProvider=new HashMap<>();}//本地注册服务public void provideServiceInterface(Object service){String serviceName=service.getClass().getName();Class<?>[] interfaceName=service.getClass().getInterfaces();for (Class<?> clazz:interfaceName){interfaceProvider.put(clazz.getName(),service);}}//获取服务实例public Object getService(String interfaceName){return interfaceProvider.get(interfaceName);}
}

3.11 客户端主程序

public class TestClient {public static void main(String[] args) {ClientProxy clientProxy=new ClientProxy("127.0.0.1",9999);UserService proxy=clientProxy.getProxy(UserService.class);User user = proxy.getUserByUserId(1);System.out.println("从服务端得到的user="+user.toString());User u=User.builder().id(100).userName("wxx").sex(true).build();Integer id = proxy.insertUserId(u);System.out.println("向服务端插入user的id"+id);}
}

3.12 服务端主程序

public class TestServer {public static void main(String[] args) {UserService userService=new UserServiceImpl();ServiceProvider serviceProvider=new ServiceProvider();serviceProvider.provideServiceInterface(userService);RpcServer rpcServer=new SimpleRPCRPCServer(serviceProvider);rpcServer.start(9999);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/357479.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录-Day37

56. 合并区间 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 示例 1&#xff1a; 输入&#xff1a;in…

安装react之nvm版本低引起的问题

1.背景 准备搭建一个react&#xff0c;然后看官网文档 创建项目&#xff0c;使用命令行 npx create-next-applatest 创建项目的流程都是正常的。当我准备运行项目的时候&#xff0c;报错了 原先的报错没有了&#xff0c;从网上找了一个类似的 重要的内容是&#xff1a;当前…

网络技术原理需要解决的5个问题

解决世界上任意两台设备时如何通讯的&#xff1f;&#xff1f; 第一个问题&#xff0c;pc1和pc3是怎么通讯的&#xff1f; 这俩属于同一个网段&#xff0c;那么同网段的是怎么通讯的&#xff1f; pc1和pc2属于不同的网段&#xff0c;第二个问题&#xff0c;不同网段的设备是…

【5】apollo编写python节点步骤及实例

在workspace/modules下新建包buildtool create --template component modules/test_one 编译包 buildtool build -p modules/test_two/ 增加自己的proto消息 在刚才自动生成的proto文件里面添加自己定义的消息,记得重新编译. syntax "proto2";package apollo;…

UltraEditUEStudio软件安装包下载及安装教程

​根据软件大数据显示提供预定义的或使用者创建的编辑“环境”&#xff0c;能记住 UltraEdit 的所有可停靠窗口、工具栏等的状态。实际上我们可以这样讲HTML 工具栏&#xff0c;对常用的 HTML 功能作了预配置;文件加密/解密;多字节和集成的 IME。根据使用者情况表明Git Editor&…

vue3项目使用Electron打包成exe的方法与打包报错解决

将vue3项目打包成exe文件方法 一、安装 1.安装electron npm install electron --save-devnpm install electron-builder --save-dev 2.在vue项目根目录新建文件index.js // index.js// Modules to control application life and create native browser window const { app…

font-spider按需生成字体文件

font-spider可以全局安装,也可以单个项目内安装,使用npm run xxxx的形式 npm i font-spider "dev": "font-spider ./*.html" <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name&…

什么是APP分发-了解APP分发的核心概念

APP分发的定义和意义 大家有没有过这样的经历&#xff1a;辛辛苦苦开发了一款APP&#xff0c;却不知道该怎么让更多人知道和使用&#xff1f;APP分发的重要性就凸显出来了。APP分发就是将你的应用推送到不同的应用市场和平台&#xff0c;让更多用户能够下载和使用。 小猪app封…

[机器学习算法]支持向量机

支持向量机&#xff08;SVM&#xff09;是一种用于分类和回归分析的监督学习模型。SVM通过找到一个超平面来将数据点分开&#xff0c;从而实现分类。 1. 理解基本概念和理论&#xff1a; 超平面&#xff08;Hyperplane&#xff09;&#xff1a;在高维空间中&#xff0c;将数据…

【Python机器学习】k均值聚类——k均值的失败案例

k均值可能不总能找到“正确”的簇个数&#xff0c;每个簇仅由其中心定义&#xff0c;这意味着每个簇都是凸形。因此&#xff0c;k均值只能找到相对简单的形状。k均值还假设所有簇在某种程度上具有相同的“直径”&#xff0c;它总是将簇之间的边界刚好画在簇中心的之间位置。有时…

python使用pywebview打造一个现代化的可视化GUI界面

&#x1f308;所属专栏&#xff1a;【python】✨作者主页&#xff1a; Mr.Zwq✔️个人简介&#xff1a;一个正在努力学技术的Python领域创作者&#xff0c;擅长爬虫&#xff0c;逆向&#xff0c;全栈方向&#xff0c;专注基础和实战分享&#xff0c;欢迎咨询&#xff01; 您的…

海洋生物识别系统+图像识别+Python+人工智能课设+深度学习+卷积神经网络算法+TensorFlow

一、介绍 海洋生物识别系统。以Python作为主要编程语言&#xff0c;通过TensorFlow搭建ResNet50卷积神经网络算法&#xff0c;通过对22种常见的海洋生物&#xff08;‘蛤蜊’, ‘珊瑚’, ‘螃蟹’, ‘海豚’, ‘鳗鱼’, ‘水母’, ‘龙虾’, ‘海蛞蝓’, ‘章鱼’, ‘水獭’, …

Linux配置中文环境

文章目录 前言中文语言包中文输入法中文字体 前言 在Linux系统中修改为中文环境&#xff0c;通常涉及以下几个步骤&#xff1a; 中文语言包 更新源列表&#xff1a; 更新系统的软件源列表和语言环境设置&#xff0c;确保可以安装所需的语言包。 sudo apt update sudo apt ins…

《计算机英语》 Unit 3 Software Engineering 软件工程

Section A Software Engineering Methodologies 软件工程方法论 Software development is an engineering process. 软件开发是一个工程过程。 The goal of researchers in software engineering is to find principles that guide the software development process and lea…

如何选择服务器?快解析能搭建私人服务器吗?

随着网络的发展&#xff0c;搭建私人服务器逐渐成为网络达人们的热门选择&#xff0c;比如建立私人性质的博客、论坛、FTP、个人网站、服务器集群等。通过源搭建私人服务器&#xff0c;就可以将很多资源分享到网络上进行信息共享。随之而来的是服务器市场不断扩大&#xff0c;在…

网络安全:Web 安全 面试题.(XSS)

网络安全&#xff1a;Web 安全 面试题.&#xff08;XSS&#xff09; 网络安全面试是指在招聘过程中,面试官会针对应聘者的网络安全相关知识和技能进行评估和考察。这种面试通常包括以下几个方面&#xff1a; &#xff08;1&#xff09;基础知识:包括网络基础知识、操作系统知…

# Kafka_深入探秘者(1):初识 kafka

Kafka_深入探秘者&#xff08;1&#xff09;&#xff1a;初识 kafka 一、kafka 特性 1、Kafka &#xff1a;最初是由 Linkedln 公司采用 Scala 语言开发的一个多分区、多副本并且基于 ZooKeeper 协调的分布式消息系统&#xff0c;现在已经捐献给了 Apache 基金会。目前 Kafka…

CentOS系统查看版本的各个命令

cat /etc/centos-release 查看CentOS版本 uname -a 命令的结果分别代表&#xff1a;当前系统的内核名称、主机名、内核发型版本、节点名、系统时间、硬件名称、硬件平台、处理器类型以及操作系统名称 cat /proc/version 命令用于查看Linux内核的版本信息。执行该命令后&#xf…

【Linux基础】SSH登录

SSH简介 安全外壳协议&#xff08;Secure Shell Protocol&#xff0c;简称SSH&#xff09;是一种加密的网络传输协议&#xff0c;可在不安全的网络中为网络服务提供安全的传输环境。 SSH通过在网络中建立安全隧道来实现SSH客户端与服务器之间的连接。 SSH最常见的用途是远程登…

为什么用excel求出的和是错误的?

Excel中求和结果错误的原因可能有几种常见的情况&#xff1a;1. **数据格式问题**&#xff1a;有时候数字可能被错误地视为文本格式。这种情况下&#xff0c;Excel 在求和时会忽略这些单元格。你可以通过将这些单元格的格式改为数值格式来解决。2. **隐藏的行或列**&#xff1a…