RTOS之邮箱

邮箱

邮箱 (Mailbox) 服务是实时操作系统中一种常用的线程间通信机制。它提供了一种高效、低开销的消息传递方式,允许线程之间交换固定大小的数据。

1. 邮箱的应用场景

考虑一个简单的示例:线程 1 负责检测按键状态并将状态信息发送出去,线程 2 接收按键状态信息并根据按键状态控制 LED 的亮灭。在这种场景下,线程 1 可以将按键状态作为邮件发送到邮箱,线程 2 从邮箱中读取邮件并执行相应的 LED 控制操作。

此外,邮箱服务也支持多线程发送。例如,存在三个线程,线程 1 发送按键状态,线程 2 发送 ADC 采样数据,而线程 3 则根据接收到的邮件类型执行不同的操作。

2. 邮箱的工作机制

RT-Thread 的邮箱服务用于线程间的异步通信,其特点是开销较低,效率较高。邮箱中的每封邮件只能存储固定大小的数据,在 32 位系统中,这个大小为 4 字节(正好可以容纳一个指针)。邮箱可以被看作是一个消息交换中心。线程或中断服务例程将 4 字节的邮件发送到邮箱中,而一个或多个线程可以从邮箱中接收这些邮件并进行处理。

邮箱工作示意图
邮箱工作示意图

邮件发送操作分为非阻塞和阻塞两种模式。非阻塞模式的邮件发送可安全应用于中断服务例程中,是线程、中断服务例程和定时器向线程发送消息的有效方式。邮件接收操作通常可能是阻塞的,这取决于邮箱中是否有邮件以及接收时设置的超时时间。当邮箱为空且超时时间不为 0 时,邮件接收操作会变为阻塞模式。在这种情况下,邮件接收操作只能由线程执行。

  • 邮件发送: 当线程向邮箱发送邮件时,如果邮箱未满,邮件会被复制到邮箱中。如果邮箱已满,发送线程可以选择设置超时时间并进入等待状态,直到邮箱有空闲位置,或直接返回 -RT_EFULL 错误。如果发送线程选择等待,当邮箱中的邮件被接收而空出空间时,等待的发送线程会被唤醒并继续发送。

  • 邮件接收: 当线程从邮箱接收邮件时,如果邮箱为空,接收线程可以选择等待直到收到新的邮件,或设置超时时间。当超时时间到达,邮箱仍未收到邮件时,等待的接收线程会被唤醒并返回 -RT_ETIMEOUT 错误。如果邮箱中有邮件,接收线程会将邮箱中的 4 字节邮件复制到接收缓存区中。

3. 邮箱控制块

在 RT-Thread 中,邮箱控制块是用于管理邮箱的数据结构,用结构体 struct rt_mailbox 表示。另一种 C 表达方式 rt_mailbox_t 表示邮箱的句柄,其本质是一个指向 struct rt_mailbox 结构体的指针。邮箱控制块的详细定义如下:

struct rt_mailbox
{

    struct rt_ipc_object parent;

    rt_uint32_t* msg_pool;       /* 邮箱缓冲区的起始地址 */
    rt_uint16_t size;            /* 邮箱缓冲区的大小(邮件数量) */

    rt_uint16_t entry;           /* 邮箱中邮件的数目 */
    rt_uint16_t in_offset;       /* 邮箱缓冲区的写入偏移量 */
    rt_uint16_t out_offset;      /* 邮箱缓冲区的读取偏移量 */
    rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */
};
typedef struct rt_mailboxrt_mailbox_t;

rt_mailbox 对象继承自 rt_ipc_object,由 IPC 容器进行管理。

4. 邮箱的管理方式

邮箱控制块结构体中包含邮箱管理的关键参数。对邮箱的操作包括:创建/初始化、发送邮件、接收邮件以及删除/脱离邮箱。

邮箱相关接口
邮箱相关接口
4.1 创建和删除邮箱
4.1.1 创建动态邮箱

可以使用 rt_mb_create() 函数动态创建一个邮箱对象:

rt_mailbox_t rt_mb_create(const char* name, rt_size_t size, rt_uint8_t flag);

此函数首先从对象管理器中分配一个邮箱对象,然后动态分配一块内存空间用于存储邮件,该内存空间大小为 size * 4 字节。接着,初始化邮件计数和发送偏移量。rt_mb_create() 函数的参数和返回值说明如下:

参数描述
name邮箱名称。
size邮箱容量,即邮箱可以存储的邮件数量。
flag邮箱标志,取值可以为 RT_IPC_FLAG_FIFORT_IPC_FLAG_PRIORT_IPC_FLAG_FIFO 表示先进先出, RT_IPC_FLAG_PRIO 表示优先级调度。
返回值描述
RT_NULL创建失败。
邮箱对象的句柄创建成功,返回邮箱对象的句柄(邮箱控制块指针)。

注意: RT_IPC_FLAG_FIFO 属于非实时调度方式,除非应用程序非常在意先进先出,且清楚地知道所有涉及该邮箱的线程将变为非实时线程,否则建议使用 RT_IPC_FLAG_PRIO,以保证线程的实时性。

4.1.2 删除动态邮箱

当使用 rt_mb_create() 创建的邮箱不再使用时,应该调用 rt_mb_delete() 函数删除邮箱以释放系统资源:

rt_err_t rt_mb_delete(rt_mailbox_t mb);

删除邮箱时,如果存在挂起在该邮箱上的线程,内核会先唤醒所有挂起线程(返回错误码 -RT_ERROR),然后释放邮箱使用的内存,最后删除邮箱对象。rt_mb_delete() 函数的参数和返回值说明如下:

参数描述
mb邮箱对象的句柄。
返回值描述
RT_EOK删除成功。
4.2 初始化和脱离邮箱
4.2.1 初始化静态邮箱

可以使用 rt_mb_init() 函数初始化静态邮箱对象:

rt_err_t rt_mb_init(rt_mailbox_t mb,
                    const char* name,
                    void* msgpool,
                    rt_size_t size,
                    rt_uint8_t flag)
;

rt_mb_create() 不同,静态邮箱对象的内存是由编译器在编译时分配的,通常位于读写数据段或未初始化数据段。rt_mb_init() 需要传入用户已经分配好的邮箱控制块、缓冲区指针、邮箱名称和邮箱容量(邮件数量)。rt_mb_init() 函数的参数和返回值说明如下:

参数描述
mb邮箱对象的句柄(邮箱控制块指针)。
name邮箱名称。
msgpool邮箱缓冲区指针。
size邮箱容量,即邮箱可以存储的邮件数量。
flag邮箱标志,取值可以为 RT_IPC_FLAG_FIFORT_IPC_FLAG_PRIORT_IPC_FLAG_FIFO 表示先进先出, RT_IPC_FLAG_PRIO 表示优先级调度。
返回值描述
RT_EOK初始化成功。

size 参数指定的邮箱容量,实际上是 msgpool 指向的缓冲区可以容纳的邮件数量。如果 msgpool 指向的缓冲区的字节数为 N,则邮箱容量应为 N / 4

4.2.2 脱离静态邮箱

可以使用 rt_mb_detach() 函数将静态初始化的邮箱对象从内核对象管理器中脱离:

rt_err_t rt_mb_detach(rt_mailbox_t mb);

rt_mb_detach() 会先唤醒所有挂起在该邮箱上的线程(线程返回错误码 -RT_ERROR),然后将邮箱对象从内核对象管理器中移除。rt_mb_detach() 函数的参数和返回值说明如下:

参数描述
mb邮箱对象的句柄。
返回值描述
RT_EOK脱离成功。
4.3 发送邮件

线程或中断服务程序可以使用 rt_mb_send() 函数向邮箱发送邮件:

rt_err_t rt_mb_send(rt_mailbox_t mb, rt_uint32_t value);

发送的邮件可以是任意 32 位格式的数据,例如整数值或指向缓冲区的指针。当邮箱已满时,发送线程或中断程序将收到 -RT_EFULL 返回值。rt_mb_send() 函数的参数和返回值说明如下:

参数描述
mb邮箱对象的句柄。
value邮件内容 (32 位数据)。
返回值描述
RT_EOK发送成功。
-RT_EFULL邮箱已满。
4.4 等待方式发送邮件

可以使用 rt_mb_send_wait() 函数以等待方式向指定邮箱发送邮件:

rt_err_t rt_mb_send_wait(rt_mailbox_t mb,
                      rt_uint32_t value,
                      rt_int32_t timeout)
;

rt_mb_send_wait()rt_mb_send() 的区别在于增加了超时等待功能。如果邮箱已满,发送线程会根据 timeout 参数等待,直到邮箱有空闲位置或超时。如果超时时间到达仍没有空闲位置,发送线程将被唤醒并返回错误码 -RT_ETIMEOUTrt_mb_send_wait() 函数的参数和返回值说明如下:

参数描述
mb邮箱对象的句柄。
value邮件内容。
timeout超时时间。
返回值描述
RT_EOK发送成功。
-RT_ETIMEOUT超时。
-RT_ERROR发送失败。
4.5 发送紧急邮件

可以使用 rt_mb_urgent() 函数发送紧急邮件:

rt_err_t rt_mb_urgent(rt_mailbox_t mb, rt_ubase_t value);

发送紧急邮件的操作与普通发送邮件类似。不同之处在于,发送紧急邮件时,邮件会被直接插入到邮件队列的头部,这样接收者就可以优先接收到紧急邮件并及时处理。rt_mb_urgent() 函数的参数和返回值说明如下:

参数描述
mb邮箱对象的句柄。
value邮件内容。
返回值描述
RT_EOK发送成功。
-RT_EFULL邮箱已满。
4.6 接收邮件

接收线程可以使用 rt_mb_recv() 函数从邮箱接收邮件:

rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_uint32_t* value, rt_int32_t timeout);

当邮箱中有邮件时,接收线程会立即读取邮件并返回 RT_EOK。否则,接收线程会根据 timeout 参数决定,要么挂起在邮箱的等待队列上,要么直接返回。如果设置了超时时间,在指定时间内仍未收到邮件,则会返回 -RT_ETIMEOUT 错误。 rt_mb_recv() 函数的参数和返回值说明如下:

参数描述
mb邮箱对象的句柄。
value指向邮件存储位置的指针。
timeout超时时间。
返回值描述
RT_EOK接收成功。
-RT_ETIMEOUT超时。
-RT_ERROR接收失败。
5. 邮箱使用示例

以下示例模拟一个“外卖点餐系统”,其中:

  • “顾客”线程 (customer_thread): 模拟顾客点餐,并将订单发送到邮箱。
  • “餐厅”线程 (restaurant_thread): 模拟餐厅接收订单,并根据订单内容进行处理。
#include <rtthread.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define THREAD_PRIORITY         10
#define THREAD_STACK_SIZE       1024
#define THREAD_TIMESLICE        5

/* 邮箱控制块 */
static struct rt_mailbox order_mb;
/* 邮箱内存池 */
static char order_mb_pool[128];


/* 定义一个订单结构体 */
typedef struct
{

    char dish[32];
    int quantity;
    char note[64];
} Order;


/* 顾客线程 */
static void customer_thread_entry(void *parameter)
{
    Order my_order;
    int order_count = 0;

    while (order_count < 3)
    {
        order_count++;
        rt_kprintf("顾客: 准备订单 #%d...\n", order_count);

        // 模拟顾客点餐
        if(order_count == 1)
        {
        strcpy(my_order.dish, "宫保鸡丁");
        my_order.quantity = 2;
        strcpy(my_order.note, "不要太辣!");
        }
        else if(order_count == 2)
        {
            strcpy(my_order.dish, "麻婆豆腐");
            my_order.quantity = 1;
            strcpy(my_order.note, "多加点辣!");

        }
        else
        {
           strcpy(my_order.dish, "蛋炒饭");
            my_order.quantity = 3;
            strcpy(my_order.note, "加鸡蛋!");

        }
        // 将订单复制到堆区,避免栈数据失效
         Order *order_ptr = (Order*)rt_malloc(sizeof(Order));
        if(order_ptr == RT_NULL)
        {
            rt_kprintf("顾客: 内存分配失败!\n");
            continue;
        }
        memcpy(order_ptr,&my_order,sizeof(Order));
        // 发送订单到邮箱
        if (rt_mb_send(&order_mb, (rt_uint32_t)order_ptr) == RT_EOK)
        {
             rt_kprintf("顾客: 订单 #%d 发送给餐厅: %s, 数量: %d\n",
                       order_count, order_ptr->dish, order_ptr->quantity);
        }
        else
        {
            rt_kprintf("顾客: 发送订单给餐厅失败!\n");
              rt_free(order_ptr); // 释放内存
        }


        // 模拟顾客等待其他顾客点餐
          rt_thread_mdelay(rt_tick_from_millisecond(1000));
    }

    rt_kprintf("顾客: 今天就到这里了!\n");

}


/* 餐厅线程 */
static void restaurant_thread_entry(void *parameter)
{
    Order *received_order;
    int order_received = 0;

    while (1)
    {
        // 从邮箱中接收订单
        if (rt_mb_recv(&order_mb, (rt_uint32_t *)&received_order, RT_WAITING_FOREVER) == RT_EOK)
        {
           order_received++;
           rt_kprintf("餐厅: 收到订单 #%d! 菜品: %s, 数量: %d, 备注: %s\n",
                       order_received, received_order->dish, received_order->quantity, received_order->note);
           // 模拟餐厅处理订单(这里只是打印消息)
            rt_thread_mdelay(rt_tick_from_millisecond(500));

             // 处理完订单后释放内存
            rt_free(received_order);

        }
    }
}


int main(void)
{
    rt_err_t result;

    /* 初始化邮箱 */
    result = rt_mb_init(&order_mb,
                        "order_mb",
                        &order_mb_pool[0],
                        sizeof(order_mb_pool) / 4,
                        RT_IPC_FLAG_FIFO);

    if (result != RT_EOK)
    {
        rt_kprintf("邮箱初始化失败!\n");
        return -1;
    }

    /* 创建顾客线程 */
    rt_thread_t customer_thread = rt_thread_create("customer",
                                                     customer_thread_entry,
                                                     RT_NULL,
                                                     THREAD_STACK_SIZE,
                                                     THREAD_PRIORITY,
                                                     THREAD_TIMESLICE);

    if (customer_thread != RT_NULL)
    {
        rt_thread_startup(customer_thread);
    }

    /* 创建餐厅线程 */
    rt_thread_t restaurant_thread = rt_thread_create("restaurant",
                                                      restaurant_thread_entry,
                                                      RT_NULL,
                                                      THREAD_STACK_SIZE,
                                                      THREAD_PRIORITY,
                                                      THREAD_TIMESLICE);

    if (restaurant_thread != RT_NULL)
    {
        rt_thread_startup(restaurant_thread);
    }

    return 0;
}

实验现象:
邮箱使用示例实验现象
邮箱使用示例实验现象
6. 邮箱的应用场合

邮箱是一种简单高效的线程间消息传递方式。在 RT-Thread 中,邮箱可以传递一个 4 字节大小的邮件,并且邮箱具有一定的缓冲功能。邮箱的容量决定了它可以缓存的邮件数量。

由于邮箱每次只能传递 4 字节数据,所以它适用于传递小于等于 4 字节的消息。在 32 位系统中,4 字节恰好可以容纳一个指针,因此可以利用邮箱传递指向缓冲区的指针,从而间接实现传递较大的消息。例如:

struct msg
{

    rt_uint8_t *data_ptr;
    rt_uint32_t data_size;
};

当一个线程需要传递以上结构体 msg 时,可以先动态分配结构体,再将指向数据的指针 data_ptr 和数据块长度 data_size 写入,然后将指向这个结构体的指针作为邮件发送到邮箱中:

struct msgmsg_ptr;

msg_ptr = (struct msg*)rt_malloc(sizeof(struct msg));
msg_ptr->data_ptr = ...; /* 指向相应的数据块地址 */
msg_ptr->data_size = len; /* 数据块的长度 */
/* 发送这个消息指针给 mb 邮箱 */
rt_mb_send(mb, (rt_uint32_t)msg_ptr);

接收线程在接收邮件时,需要将接收到的指针转换为 struct msg* 类型,使用完成后,需要释放动态分配的内存:

struct msgmsg_ptr;
if (rt_mb_recv(mb, (rt_uint32_t*)&msg_ptr) == RT_EOK)
{
    /* 在接收线程处理完毕后,需要释放相应的内存块 */
    rt_free(msg_ptr);
}

好的,这次的内容就到这里啦

感谢你的阅读,欢迎点赞、关注、转发

我们,下次再见!

本文使用 markdown.com.cn 排版

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

凯酷全科技抖音电商服务的卓越践行者

在数字经济蓬勃发展的今天&#xff0c;电子商务已成为企业增长的新引擎。随着短视频平台的崛起&#xff0c;抖音作为全球领先的短视频社交平台&#xff0c;不仅改变了人们的娱乐方式&#xff0c;也为品牌和商家提供了全新的营销渠道。厦门凯酷全科技有限公司&#xff08;以下简…

AI的进阶之路:从机器学习到深度学习的演变(三)

&#xff08;承接上集&#xff1a;AI的进阶之路&#xff1a;从机器学习到深度学习的演变&#xff08;二&#xff09;&#xff09; 四、深度学习&#xff08;DL&#xff09;&#xff1a;机器学习的革命性突破 深度学习&#xff08;DL&#xff09;作为机器学习的一个重要分支&am…

数据集-目标检测系列 车牌检测识别 数据集 CCPD2019

车牌检测&识别 数据集 CCPD2019 DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加中。 需要更多数据资源和技术解决方案&#xff0c;知识星球&#xff1a; “DataBall - X 数据球(free)” 贵在坚持&#xff01; 数据样…

安全算法基础(一)

安全算法是算法的分支之一&#xff0c;还的依靠大量的数学基础进行计算&#xff0c;本文参照兜哥的AI安全样本对抗&#xff0c;做一个简单的算法安全概括&#xff0c;从零学习。 最新的安全算法对于我们常规的攻击样本检测&#xff0c;效果是不理想的&#xff0c;为了探究其原…

[SZ901]JTAG高速下载设置(53Mhz)

SZ901最高支持JTAG 53MHz的时钟频率&#xff0c;下载bit文件和固化程序的速度提升非常明显。 首先设置参数 1&#xff0c;将JTAG0 分频系数修改为3 2&#xff0c;设置参数&#xff0c;更新参数。&#xff08;完成&#xff09; 打开VIVADO VIVADO 正常识别FPGA&#xff0c;速…

图漾相机-ROS1_SDK_ubuntu版本编译(新版本)

文章目录 官网编译文档链接官网SDK下载链接1、下载 Camport ROS1 SDK1.下载git2、下载链接 2、准备编译工作1、安装 catkin2、配置环境变量3. 将Camport3中的linux库文件拷贝到 user/lib目录下4、修改lunch文件制定相机&#xff08;可以放在最后可以参考在线文档&#xff09;**…

openbmc hwmon与sensor监控

1.说明 参考文档: https://github.com/openbmc/entity-manager/blob/master/docs/entity_manager_dbus_api.mdhttps://github.com/openbmc/entity-manager/blob/master/docs/my_first_sensors.md 1.1 简单介绍 注意: 本节是快速浏览整个sensor框架&#xff0c;了解大致open…

Java --- 多线程

目录 前言&#xff1a; 一.线程的创建&#xff1a; 1.通过继承 Thread 类来创建线程&#xff1a; 2.通过Runnable接口创建线程&#xff1a; 3.通过Java8引入的lambda语法&#xff1a; 线程的优先级&#xff1a; 二.线程的生命周期&#xff1a; 三. 中断线程&#xff1a…

使用 acme.sh 申请域名 SSL/TLS 证书完整指南

使用 acme.sh 申请域名 SSL/TLS 证书完整指南 简介为什么选择 acme.sh 和 ZeroSSL&#xff1f;前置要求安装过程 步骤一&#xff1a;安装 acme.sh步骤二&#xff1a;配置 ZeroSSL 证书申请 方法一&#xff1a;手动 DNS 验证&#xff08;推荐新手使用&#xff09;方法二&#xf…

Flutter组件————Scaffold

Scaffold Scaffold 是一个基础的可视化界面结构组件&#xff0c;它实现了基本的Material Design布局结构。使用 Scaffold 可以快速地搭建起包含应用栏&#xff08;AppBar&#xff09;、内容区域&#xff08;body&#xff09;、抽屉菜单&#xff08;Drawer&#xff09;、底部导…

YOLOv8目标检测(七)_AB压力测试

YOLOv8目标检测(一)_检测流程梳理&#xff1a;YOLOv8目标检测(一)_检测流程梳理_yolo检测流程-CSDN博客 YOLOv8目标检测(二)_准备数据集&#xff1a;YOLOv8目标检测(二)_准备数据集_yolov8 数据集准备-CSDN博客 YOLOv8目标检测(三)_训练模型&#xff1a;YOLOv8目标检测(三)_训…

SpringBoot的创建方式

SpringBoot创建的五种方式 1.通过Springboot官网链接下载 注意SpringBoot项目的封装方式默认为Jar 需要查看一下&#xff0c;自己的Maven版本是否正确 创建成功 2.通过 aliyun官网链接下载 修改服务路径为阿里云链接 创建成功 3.通过Springboot官网下载 点击&#xff0c;拉到最…

批处理理解

初识批处理 如何批处理&#xff1a; 命名&#xff1a;.bat 方法&#xff1a;创建一个记事本文件&#xff0c;然后将其扩展改为.bat 批处理作用&#xff1a;自上而下成批处理每一条DOS命令&#xff0c;直到执行到最后一条。运行环境&#xff1a;当然是我们cmd了 回归我学过的…

概率论得学习和整理29: 用EXCEL 描述二项分布

目录 1 关于二项分布的基本内容 2 二项分布的概率 2.1 核心要素 2.2 成功K次的概率&#xff0c;二项分布公式 2.3 期望和方差 2.4 具体试验 2.5 概率质量函数pmf 和cdf 3 二项分布的pmf图的改进 3.1 改进折线图 3.2 如何生成这种竖线图呢 4 不同的二项分布 4.1 p0.…

全志H618 Android12修改doucmentsui功能菜单项

背景: 由于当前的文件管理器在我们的产品定义当中,某些界面有改动的需求,所以需要在Android12 rom中进行定制以符合当前产品定义。 需求: 在进入File文件管理器后,查看...功能菜单时,有不需要的功能菜单,需要隐藏,如:新建窗口、不显示的文件夹、故代码分析以及客制…

Elasticsearch问题总结

Fielddata access on the_id field is disallowed, you can re-enable it by updating the dynamic cluster setting: indices.id_field_data.enabledElasticsearch默认禁用_id字段进行排序&#xff0c;这是因为_id字段通常不需要进行聚合或排序操作&#xff0c;启用字段数据可…

基于WCF(C#)+SQL SERVER设计与实现的在线评测系统

基于WCF和SQL SERVER的在线评测系统设计与实现 摘要 目前&#xff0c;在线评测系统大多采用Linux系统作为运行平台&#xff0c;由于Linux系统人机交互能力差&#xff0c;使得系统部署要求高和维护难度大。本文针对以上问题进行分析&#xff0c;采用Windows操作系统作为运行平…

【C++图论】1993. 树上的操作|1861

本文涉及知识点 C图论 LeetCode 1993. 树上的操作 给你一棵 n 个节点的树&#xff0c;编号从 0 到 n - 1 &#xff0c;以父节点数组 parent 的形式给出&#xff0c;其中 parent[i] 是第 i 个节点的父节点。树的根节点为 0 号节点&#xff0c;所以 parent[0] -1 &#xff0c…

如何使用Python WebDriver爬取ChatGPT内容(完整教程)

大背景 虽然我们能用网页版chatGPT来聊天、写文章&#xff0c;但是我们采集大量的内容&#xff0c;就得不断地手动输入提问来获取答案&#xff0c;并且将结果复制到数据库来保存。如果整个过程能使用程序来做自然要节省很多的人力&#xff0c;精力和时间。 Python webdirver …

渗透测试-前端加密分析之RSA加密登录(密钥来源服务器)

本文是高级前端加解密与验签实战的第6篇文章&#xff0c;本系列文章实验靶场为Yakit里自带的Vulinbox靶场&#xff0c;本文讲述的是绕过RSA加密来爆破登录。 分析 这里的代码跟上文的类似&#xff0c;但是加密的公钥是通过请求服务端获取的 http://127.0.0.1:8787/crypto/js/…