• 【网络协议】RPC、REST API深入理解及简单demo实现


    一、RPC

    RPC远程过程调用Remote Procedure Call Protocol,简称RPC),像调用本地服务(方法)一样调用服务器的服务(方法)。通常的实现有 XML-RPC , JSON-RPC , 通信方式基本相同, 所不同的只是传输数据的格式.

    RPC是分布式架构的核心,按响应方式分如下两种:

    同步调用:客户端调用服务方方法,等待直到服务方返回结果或者超时,再继续自己的操作

    异步调用:客户端把消息发送给中间件,不再等待服务端返回,直接继续自己的操作。

    同步调用的实现方式有WebService和RMI。Web Service提供的服务是基于web容器的,底层使用http协议,因而适合不同语言异构系统间的调用。RMI实际上是Java语言的RPC实现,允许方法返回 Java 对象以及基本数据类型,适合用于JAVA语言构建的不同系统间的调用。

    异步调用的JAVA实现版就是JMS(Java Message Service),目前开源的的JMS中间件有Apache社区的ActiveMQ、Kafka消息中间件,另外有阿里的RocketMQ。

    RPC架构里包含如下4个组件:

    1. 客户端(Client):服务调用方
    2. 客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数打包成网络消息,再通过网络发送给服务方
    3. 服务端存根(Server Stub):接受客户端发送过来的消息并解包,再调用本地服务
    4. 服务端(Server):真正的服务提供者。

    具体实现步骤:

    1. 服务调用方(client)(客户端)以本地调用方式调用服务;
    2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;在Java里就是序列化的过程
    3. client stub找到服务地址,并将消息通过网络发送到服务端;
    4. server stub收到消息后进行解码,在Java里就是反序列化的过程;
    5. server stub根据解码结果调用本地的服务;
    6. 本地服务执行处理逻辑;
    7. 本地服务将结果返回给server stub;
    8. server stub将返回结果打包成消息,Java里的序列化;
    9. server stub将打包后的消息通过网络并发送至消费方
    10. client stub接收到消息,并进行解码, Java里的反序列化;
    11. 服务调用方(client)得到最终结果。

    RPC框架的目标就是把2-10步封装起来,把调用、编码/解码的过程封装起来,让用户像调用本地服务一样的调用远程服务。要做到对客户端(调用方)透明化服务, RPC框架需要考虑解决如下问题:

    1. 通讯问题 : 主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。
    2. 寻址问题 : A服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器(如主机或IP地址)以及特定的端口,方法的名称是什么,这样才能完成调用。比如基于Web服务协议栈的RPC,就要提供一个endpoint URI,或者是从UDDI服务上查找。如果是RMI调用的话,还需要一个RMI Registry来注册服务的地址。
    3. 序列化与反序列化 : 当A服务器上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议如TCP传递到B服务器,由于网络协议是基于二进制的,内存中的参数的值要序列化成二进制的形式,也就是序列化(Serialize)或编组(marshal),通过寻址和传输将序列化的二进制发送给B服务器。 同理,B服务器接收参数要将参数反序列化。B服务器应用调用自己的方法处理后返回的结果也要序列化给A服务器,A服务器接收也要经过反序列化的过程。

    二、REST

    REST即表述性状态传递Representational State Transfer,简称REST),是一种软件架构风格。REST通过HTTP协议定义的通用动词方法(GETPUTDELETEPOST) ,以URI对网络资源进行唯一标识,响应端根据请求端的不同需求,通过无状态通信,对其请求的资源进行表述。

    Rest架构的主要原则:

    1. 网络上的所有事物都被抽象为资源
    2. 每个资源都有一个唯一的资源标识符
    3. 同一个资源具有多种表现形式(xml,json等)
    4. 对资源的各种操作不会改变资源标识符
    5. 所有的操作都是无状态的

    其中表述性状态,是指(在某个瞬间状态的)资源数据的快照,包括资源数据的内容、表述格式(XML、JSON)等信息。

    其中无状态通信,是指服务端(响应端)不保存任何与特定HTTP请求相关的资源,应用状态必须由请求方在请求过程中提供。要求在网络通信过程中,任意一个Web请求必须与其他请求隔离,当请求端提出请求时,请求本身包含了响应端为响应这一请求所需的全部信息。

    REST使用HTTP+URI+XML /JSON 的技术来实现其API要求的架构风格:HTTP协议和URI用于统一接口和定位资源,文本、二进制流、XML、JSON等格式用来作为资源的表述。

    举例:

    在Restful之前的操作: 请求的地址对应具体的业务操作
    http://127.0.0.1/user/query/1 GET 根据用户id查询用户数据
    http://127.0.0.1/user/save POST 新增用户
    http://127.0.0.1/user/update POST 修改用户信息
    http://127.0.0.1/user/delete GET/POST 删除用户信息

    RESTful用法: 请求类型决定操作
    http://127.0.0.1/user/1 GET 根据用户id查询用户数据
    http://127.0.0.1/user POST 新增用户
    http://127.0.0.1/user PUT 修改用户信息
    http://127.0.0.1/user DELETE 删除用户信息

    RESTful风格的体现,在你使用了get请求,就是查询;使用post请求,就是新增的请求;使用put请求,就是修改的请求;使用delete请求,就是删除的请求。这样做就完全没有必要对crud做具体的描述。

    满足REST约束条件和原则的架构,就被称为是RESTful架构。就像URL都是URI(统一资源标识)的表现形式一样,RESTful是符合REST原则的表现形式。

    三、代码

    代码:GitHub

    3.1 RPC demo

    要实现一个简单的 RPC(远程过程调用)框架,你可以遵循以下步骤:

    1. 定义服务接口:首先定义你的服务接口,这个接口定义了客户端可以调用的远程方法。这个接口通常包含服务的所有方法签名,并且必须继承 java.rmi.Remote 接口,并且每个方法都要声明 throws RemoteException。
    2. 实现服务接口:编写实现了服务接口的具体服务类。这个类负责实现服务接口中的方法逻辑,并且承载了客户端请求的处理。
    3. 启动 RMI 注册表:在服务端,启动 RMI 注册表,以便客户端能够找到服务。可以使用java.rmi.registry.LocateRegistry类的静态方法来获取或创建 RMI 注册表。
    4. 注册远程对象:在启动 RMI 注册表后,将实现了服务接口的远程对象绑定到注册表上,以便客户端能够查找和调用。
    5. 编写客户端代码:在客户端编写代码,获取远程对象的引用,并调用远程方法。

    以下是一个简单的示例,演示了如何使用 Java RMI 实现一个简单的 RPC 框架。

    服务接口:CalculatorService.java

    import java.rmi.Remote;
    import java.rmi.RemoteException;
    
    public interface CalculatorService extends Remote {
        int add(int a, int b) throws RemoteException;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    服务实现:CalculatorServiceImpl.java

    import java.rmi.RemoteException;
    import java.rmi.server.UnicastRemoteObject;
    
    public class CalculatorServiceImpl extends UnicastRemoteObject implements CalculatorService {
    
        protected CalculatorServiceImpl() throws RemoteException {
            super();
        }
    
        @Override
        public int add(int a, int b) throws RemoteException {
            return a + b;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    服务端:CalculatorServer.java

    import java.rmi.registry.LocateRegistry;
    import java.rmi.registry.Registry;
    
    public class CalculatorServer {
    
        public static void main(String[] args) {
            try {
                CalculatorService calculatorService = new CalculatorServiceImpl();
                Registry registry = LocateRegistry.createRegistry(8081);
                registry.rebind("CalculatorService", calculatorService);
                System.out.println("服务器运行中...");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    客户端:CalculatorClient.java

    import java.rmi.registry.LocateRegistry;
    import java.rmi.registry.Registry;
    
    public class CalculatorClient {
    
        public static void main(String[] args) {
            try {
                Registry registry = LocateRegistry.getRegistry("localhost", 8081);
                CalculatorService calculatorService = (CalculatorService) registry.lookup("CalculatorService");
    
                int result = calculatorService.add(10, 5);
                System.out.println("Result of addition: " + result);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    运行结果:

    在这里插入图片描述

    3.2 Rest API demo

    CalculatorController

    @RestController
    class CalculatorController {
    
        @GetMapping("/add")
        public int add(@RequestParam int a, @RequestParam int b) {
            return a + b;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    CalculatorClient

    public class CalculatorClient {
        public static void main(String[] args) {
            RestTemplate restTemplate = new RestTemplate();
    
            String url = "http://localhost:8081/add?a=10&b=5";
            int result = restTemplate.getForObject(url, Integer.class);
    
            System.out.println("Result of addition: " + result);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行结果:

    在这里插入图片描述

  • 相关阅读:
    领先科技2024年3月5-7日第12届国际生物发酵展-宁泰橡塑
    外包的水有多深?腾讯15k的外包测试岗能去吗?
    Tomcat
    Vue3 企业级优雅实战 - 组件库框架 - 1 搭建 pnpm monorepo
    MATLAB | MATLAB中绘图的奇淫技巧合集
    Ubuntu 20.04.05安装ceres-1.14.0
    提升测试效果:深入解析《Effective软件测试》的关键方法与实践
    分页列表缓存,你真的会吗
    [C语言]通过malloc分配一段连续内存存放类似二维数组
    MySQL 基本语法讲解及示例(上)
  • 原文地址:https://blog.csdn.net/weixin_45683778/article/details/136683986