解锁LiteOS消息队列:物联网开发的通信密钥

目录

一、走进 LiteOS 消息队列

二、消息队列是什么

三、运作原理大揭秘

(一)创建队列

(二)写队列操作

(三)读队列过程

(四)删除队列步骤

四、API 函数详解

(一)创建消息队列函数 osMessageQueueNew

(二)发送消息函数 osMessageQueuePut

(三)获取消息函数 osMessageQueueGet

(四)删除消息队列函数 osMessageQueueDelete

五、应用场景展示

(一)智能路灯系统

(二)多管线程数据交换

六、使用案例分析

(一)案例背景介绍

(二)代码实现展示

(三)运行结果分析

七、总结与展望


一、走进 LiteOS 消息队列

        在物联网飞速发展的今天,各种智能设备如繁星般点缀在我们生活的每一个角落。从智能家居里自动调节温度的空调、根据环境亮度自动开关的智能灯具,到工业物联网中实时监测设备运行状态的传感器、高效调度生产流程的自动化系统,物联网设备的身影无处不在,深刻地改变着我们的生活和工作方式。而在这些智能设备的背后,有一个至关重要的角色 —— 物联网操作系统,它就像是设备的 “大脑”,负责协调和管理设备的各项功能,确保设备稳定、高效地运行。

        LiteOS 作为一款专为物联网领域打造的轻量级操作系统,近年来在物联网市场中崭露头角,备受关注。它以其卓越的性能、丰富的功能和出色的稳定性,赢得了众多开发者的青睐,被广泛应用于智能家居、工业控制、智能穿戴、车联网等多个领域。在 iteOS 强大的功能体系中,消息队列扮演着举足轻重的角色,它是实现设备内部各个模块之间、设备与设备之间高效通信和协同工作的关键组件,就像是一条条无形的 “高速公路”,让数据能够在不同的任务和进程之间快速、准确地传递。

二、消息队列是什么

        消息队列,简单来说,是一种常用于任务间通信的数据结构 ,就像是一个存放消息的 “大仓库”,各个任务或中断服务程序可以把消息放入这个 “仓库”,也能从中取出消息,实现了接收来自任务或中断的不固定长度的消息,并根据不同的接口选择传递消息是否存放在自己空间。举个生活中的例子,你可以把它想象成小区门口的快递驿站。快递员(相当于生产者任务)把包裹(消息)送到驿站(消息队列),而居民(消费者任务)则在方便的时候去驿站取包裹。这样,快递员和居民之间就不需要直接对接,而通过驿站这个中间环节来传递包裹,实现了异步处理。

        在物联是网设备中,消息队列起着至关重要的作用。以智能家居系统为例,当用户通过手机 APP 发送一个控制指令,比如打开客厅的灯光。这个指令就会作为一个消息被放入消息队列中。而负责控制灯光的任务则会从消息队列中读取这个消息,并执行相应的操作,打开灯光。这样,即使手机 APP 和灯光控制模块处于不同的任务或进程中,也能通过消息队列实现高效的通信和协同工作。

        消息队列具有以下几个重要特性:

  • 先进先出(FIFO):消息按照进入队列的先后顺序进行处理,先进入队列的消息会先被取出。这就好比在银行排队办理业务,先取号的客户会先被服务。这种顺序性保证了消息处理的公平性和有序性,特别适合一些对顺序有严格要求的场景,比如工业自动化生产中的指令执行,必须按照先后顺序依次执行,否则可能会导致生产事故。

  • 异步读写:任务可以在任何时候向队列中写入消息,而不需要等待其他任务的响应。同样,读取任务也可以在合适的时候从队列中读取消息,而不会被发送任务所阻塞。这种异步特性大大提高了系统的并发处理能力,就像我们在网上购物时,下单操作(写入消息)和商家处理订单(读取消息)是可以异步进行的,我们不需要一直等待商家处理完订单才能进行其他操作。

  • 超时机制:当任务尝试从队列中读取消息时,如果队列为空,任务可以选择等待一段时间。如果在这段时间内仍然没有消息到达,任务就会超时返回,避免无限期地等待。写入消息时也类似,如果队列已满,写入任务可以等待队列有空闲空间,或者在超时后放弃写入。比如在网络请求中,如果服务器在一定时间内没有响应,客户端就会超时返回,提示用户网络连接可能存在问题。

三、运作原理大揭秘

(一)创建队列

        在 LiteOS 中创建消息队列时,系统会根据用户传入的队列长度和消息节点大小来开辟相应的内存空间,就好比我们建造一座仓库(消息队列),需要先确定仓库的大小(队列长度)以及每个存储单元(消息节点)的尺寸。这些内存空间用于存储队列中的消息,为消息的传输和处理提供了物理载体。

        开辟完内存空间后,系统会初始化消息队列的相关信息,包括队列控制块中的各个字段。队列控制块是消息队列的 “大脑”,它保存着队列的重要信息,其中消息头节点位置 Head 和消息尾节点位置 Tail 是两个关键的变量。Head 表示队列中被占用消息的起始位置,就像仓库中第一个存放货物的位置;Tail 表示队列中被空闲消息的起始位置,也就是仓库中可以存放新货物的起始位置。刚创建队列时,Head 和 Tail 均指向队列起始位置,此时仓库还没有存放任何货物,一切都是初始状态。最后,系统会返回队列 ID,这个 ID 就像是仓库的唯一标识,后续对队列的各种操作都需要通过这个 ID 来进行,方便任务和中断服务程序准确地找到对应的消息队列 。

(二)写队列操作

        在进行写队列操作之前,系统首先会判断队列是否可写。这就好比在往仓库里存放货物之前,需要先检查仓库是否还有剩余空间。系统会根据队列控制块中的相关信息,如队列中已有的消息数量、队列的总长度等,来判断队列是否已满。如果队列已满,即没有剩余空间来存放新的消息,那么写操作将无法进行。

        当确定队列可写后,写队列操作支持两种写入方式:向队列尾节点写入和向队列头节点写入。以向队列尾节点写入为例,系统会根据 Tail 找到被占用消息节点末尾的空闲节点作为数据写入对象,就像在仓库中找到最后一件货物后面的空闲位置来存放新货物。如果 Tail 已经指向队列尾,即仓库的最后一个位置,此时就会采用回卷方式进行操作。回卷操作就像是把队列看成一个圆圈,当到达圆圈的末尾时,再回到圆圈的开头继续操作。比如队列有 10 个节点,Tail 已经指向第 10 个节点,此时需要将新消息写入第 1 个节点,就实现了回卷写入 。

(三)读队列过程

        读队列前,系统同样会进行判断,这次判断的是队列是否有消息需要读取。这类似于在从仓库取货物之前,要先确认仓库里是否有货物。如果队列为空,即没有任何消息可供读取,那么读操作将无法进行,并且读取任务可能会被挂起,直到有新的消息进入队列。

        当确定队列中有消息可读取时,系统会根据 Head 找到最先写入队列中的消息节点进行读取,就像在仓库中优先取出最早存放的货物。如果 Head 已经指向队列尾,即已经读取到了仓库中最后一件货物,此时若还需要继续读取,就会采用回卷操作。例如,Head 指向第 10 个节点,读完后需要继续读取,就会回到第 1 个节点开始读取下一个消息,从而实现消息的循环读取。

(四)删除队列步骤

        当一个消息队列不再被使用时,就需要将其删除,以释放占用的系统资源。删除队列时,系统会根据传入的队列 ID 寻找到对应的队列,这就好比根据仓库的唯一标识找到要拆除的仓库。然后,系统会把队列状态置为未使用,就像告诉其他人这个仓库已经不再使用了。接着,释放原队列所占的空间,将之前开辟的内存归还给系统,以便系统可以将这些内存重新分配给其他需要的地方。最后将对应的队列控制头置为初始状态,为下次创建队列做好准备,就像把仓库拆除后,清理场地,等待重新建造新的仓库 。

四、API 函数详解

        在 LiteOS 中,消息队列的操作是通过一系列精心设计的 API 函数来实现的,这些函数就像是开启消息队列强大功能的 “钥匙”,掌握它们是开发者在物联网应用开发中实现高效任务通信和数据传输的关键。接下来,让我们深入了解这些 API 函数的具体用法和细节 。

(一)创建消息队列函数 osMessageQueueNew

        osMessageQueueNew函数用于创建一个新的消息队列,其函数定义如下:

osMessageQueueId_t osMessageQueueNew (uint32_t msg_count,

uint32_t msg_size,

const osMessageQueueAttr_t *attr);
  • 参数说明
  • msg_count:表示队列中能够容纳的最大消息数量,它就像是一个仓库能够容纳的最大包裹数量,这个数量决定了消息队列的 “容量”。

  • msg_size:指定了每个消息的最大大小(以字节为单位),比如每个包裹的最大体积,它限制了单个消息能够携带的数据量。

  • attr:是一个指向osMessageQueueAttr_t结构体的指针,用于设置消息队列的属性。如果设置为NULL,则使用默认属性,就像我们购买商品时,如果不特别指定配置,就会得到默认配置的商品。

  • 返回值:函数成功创建消息队列后,会返回一个唯一的消息队列 ID,这个 ID 就像是仓库的门牌号,后续对该消息队列的所有操作都需要通过这个 ID 来进行;如果创建失败,返回NULL 。

        需要注意的是,这个函数不能在中断服务程序中调用,因为中断服务程序要求快速执行返回,而创建消息队列可能涉及到复杂的内存分配和初始化操作,会耗费较多时间,影响中断响应的及时性。

(二)发送消息函数 osMessageQueuePut

        osMessageQueuePut函数用于向已创建的消息队列中发送消息,函数定义为:

osStatus_t osMessageQueuePut (osMessageQueueId_t mq_id,

const void *msg_ptr,

uint8_t msg_prio,

uint32_t timeout);
  • 参数说明
  • mq_id:是由osMessageQueueNew函数创建消息队列时返回的队列 ID,通过这个 ID 可以准确找到要发送消息的目标队列,就像通过门牌号找到对应的仓库。

  • msg_ptr:指向要发送的消息的指针,它就像是指向包裹的指针,通过这个指针可以找到要发送的具体消息内容。

  • msg_prio:表示消息的优先级,高优先级的消息可能会被优先处理,就像在快递驿站中,一些加急的包裹会被优先派送。

  • timeout:指定了发送消息时的超时时间。如果队列已满,任务会等待一段时间,直到有空间可用或者超时。如果设置为 0,任务不会等待,立即返回;如果设置为osWaitForever,则会一直等待,直到消息成功发送 。

  • 返回值:发送成功时返回0(即osOK),表示消息已成功放入消息队列;发送失败时返回非 0 值,具体的错误代码可以帮助开发者定位问题所在,比如可能是队列已满、队列 ID 无效等原因导致发送失败。

        当timeout参数设置为 0 时,该函数可以在中断服务例程中调用。这是因为中断服务例程不能长时间阻塞,设置为 0 可以保证函数立即返回,不会影响中断的处理流程。

(三)获取消息函数 osMessageQueueGet

        osMessageQueueGet函数用于从消息队列中获取消息,其定义如下:

osStatus_t osMessageQueueGet (osMessageQueueId_t mq_id,

void *msg_ptr,

uint8_t *msg_prio,

uint32_t timeout);
  • 参数说明
  • mq_id:同样是消息队列的 ID,用于标识要从中获取消息的队列。

  • msg_ptr:是一个指针,指向用于存储获取到的消息的缓冲区,就像准备一个空的包裹存放区,用来接收从消息队列中取出的消息。

  • msg_prio:用于返回获取到的消息的优先级,如果不需要获取优先级信息,可以将其设置为NULL。

  • timeout:和发送消息时的timeout类似,它指定了获取消息时的等待时间。如果队列为空,任务会等待一段时间,直到有消息到来或者超时。设置为 0 时,任务不会等待,立即返回;设置为osWaitForever时,会一直等待 。

  • 返回值:获取成功时返回0(即osOK),此时msg_ptr指向的缓冲区中存储了从队列中获取到的消息;获取失败时返回非 0 值,开发者可以根据返回的错误代码来排查问题,例如可能是队列为空、队列 ID 错误等原因导致获取失败。

        当timeout为 0 时,该函数也可以在中断服务例程中调用,确保中断服务例程能够快速执行,不会因为等待消息而被阻塞 。

(四)删除消息队列函数 osMessageQueueDelete

        osMessageQueueDelete函数用于删除一个不再使用的消息队列,释放相关资源,函数定义如下:

osStatus_t osMessageQueueDelete (osMessageQueueId_t mq_id);
  • 参数说明:mq_id就是要删除的消息队列的 ID,通过这个 ID 可以准确找到要删除的目标队列。

  • 返回值:删除成功返回0(即osOK),表示消息队列已被成功删除,相关资源已被释放;删除失败返回非 0 值,可能是因为队列 ID 无效,或者队列正在被其他任务使用等原因导致删除操作无法完成 。

        在实际应用中,当一个消息队列不再被需要时,及时调用这个函数删除队列是一个良好的编程习惯,这样可以避免内存泄漏和资源浪费,就像我们清理不再使用的仓库,以便释放空间用于其他用途。

五、应用场景展示

(一)智能路灯系统

        在城市的夜晚,智能路灯宛如守护城市的 “光明使者”,为行人和车辆照亮前行的道路。而 LiteOS 消息队列在智能路灯系统中扮演着关键的角色,让路灯的管理变得更加智能、高效 。

        当智能路灯系统中的光线传感器检测到环境光线强度低于预设的阈值时,就意味着夜幕降临,需要开启路灯。此时,光线传感器所在的任务会将路灯开启的消息封装好,通过osMessageQueuePut函数发送到消息队列中。这个消息就像是一封紧急的 “照明通知”,被迅速传递到消息队列这个 “信息枢纽”。

        负责控制路灯开关的任务会持续监听消息队列,通过osMessageQueueGet函数从队列中获取消息。一旦获取到路灯开启的消息,它就像是接到了战斗号角的士兵,立即执行相应的操作,通过控制电路开启路灯,让光明瞬间驱散黑暗。同样,当光线传感器检测到光线强度高于阈值,比如在黎明破晓时,就会发送路灯关闭的消息到消息队列,控制任务获取到该消息,会及时关闭路灯,避免能源的浪费 。

        当路灯的开后关状态发生变化时,系统还会通过消息队列向管理人员发送提醒消息。比如,当某一路灯出现故障无法正常开启或关闭时,控制任务会将包含路灯编号、故障状态等信息的消息发送到消息队列。专门负责消息处理和通知的任务会从队列中读取这些消息,并通过短信、邮件或者管理平台的弹窗等方式,及时将路灯的异常状态告知管理人员。理人员在收到提醒后,能够迅速安排维修人员前往处理,确保路灯系统的正常运行,保障城市夜晚的交通安全 。

(二)多管线程数据交换

        在复杂的多线程程序中,各个线程就像是不同的 “工作小组”,它们需要协同工作,共同完成复杂的任务。而消息队列则像是这些 “工作小组” 之间传递任务和数据的 “传送带”,让线程之间的数据交换更加顺畅,大大提高了程序的并发性和效率 。

        以一个图像识别处理系统为例,系统中可能存在多个线程,其中一个线程负责从摄像头实时采集图像数据,它就像是一个不知疲倦的 “采集员”,不断地获取图像信息。当采集到一帧图像后,采集线程会将这帧图像数据封装成一个消息,通过osMessageQueuePut函数发送到消息队列中。这个消息就像是装满 图像宝藏” 的包裹,被放置到 “传送带” 上 。

        另一个线程则负“责对图像进行识别处理,它是一个专业的 “分析员”,会从消息队列中通过osMessageQueueGet函数获取图像消息。一旦获取到消息,它就会立即对图像进行分析处理,比如识别图像中的物体、判断场景等。通过消息队列,采集线程和处理线程实现了高效的数据交换,采集线程不需要等待处理线程完成处理后再采集新的图像,处理线程也能及时获取到新的图像进行处理,两者可以同时工作,大大提高了整个图像识别系统的处理速度和效率 。

        再比如,在一个多线程的网络服务器程序中,接受客户端连接的线程可以将新连接的客户端信息通过消息队列发送给处理客户端请求的线程。这样,不同的线程可以专注于自己的任务,通过消息队列进行高效的数据交互,使得服务器能够同时处理多个客户端的请求,提升了服务器的并发处理能力,为用户提供更加快速、稳定的服务 。

六、使用案例分析

(一)案例背景介绍

        为了更直观地理解 LiteOS 消息队列在实际项目中的应用,我们以一个基于 LiteOS 的智能家居环境监测系统为例。在这个系统中,多个传感器负责采集环境数据,如温度传感器实时监测室内温度,湿度传感器捕捉空气湿度信息,烟雾传感器时刻警惕火灾隐患。这些传感器分布在房屋的各个角落,如同一个个敏锐的 “小卫士”,不断收集着环境数据 。

        数据处理模块则像是一位 “智慧大脑”,负责接收传感器传来的数据,并进行分析和处理。当检测到温度过高时,它会根据预设的规则和逻辑,生成相应的控制指令,比如控制空调开启制冷模式,以调节室内温度;当检测到烟雾浓度超标时,会立即触发警报系统,通知用户可能存在火灾风险 。

        在这个智能家居环境监测系统中,消息队列就像是一条高效的 “信息高速公路”,连接着传感器和数据处理模块。传感器将采集到的数据封装成消息,通过消息队列快速传递给数据处理模块。数据处理模块则从消息队列中读取这些消息,进行相应的处理和决策。通过消息队列,传感器和数据处理模块实现了高效的异步通信,即使传感器频繁地采集数据,数据处理模块也能有条不紊地进行处理,不会因为数据的大量涌入而导致系统崩溃 。

(二)代码实现展示

        下面是创建消息队列、发送消息和接收消息的关键代码示例:

#include "cmsis_os2.h"

#include <stdio.h>

// 定义消息结构体

typedef struct {

float value;

uint8_t sensor_type;

} SensorMessage;

osMessageQueueId_t sensorQueue;

// 创建消息队列

void createMessageQueue() {

osMessageQueueAttr_t attr;

attr.attr_bits = 0U;

attr.cb_mem = NULL;

attr.cb_size = 0U;

attr.mq_mem = NULL;

attr.mq_size = 0U;

attr.name = "SensorQueue";

sensorQueue = osMessageQueueNew(10, sizeof(SensorMessage), &attr);

if (sensorQueue == NULL) {

printf("Failed to create message queue!\n");

// 处理创建失败的情况,比如返回错误码或进行其他操作

}

}

// 模拟传感器任务发送消息

void sensorTask(void *argument) {

SensorMessage msg;

while (1) {

// 模拟传感器采集数据

msg.value = 25.5; // 假设当前温度为25.5摄氏度

msg.sensor_type = 1; // 假设1代表温度传感器

// 发送消息到队列

if (osMessageQueuePut(sensorQueue, &msg, 0U, 0U) != osOK) {

printf("Failed to send message to queue!\n");

// 处理发送失败的情况,比如记录日志或进行重试

}

osDelay(1000); // 模拟传感器每隔1秒采集一次数据

}

}

// 数据处理任务接收消息

void dataProcessingTask(void *argument) {

SensorMessage receivedMsg;

while (1) {

// 从队列中接收消息

osStatus_t status = osMessageQueueGet(sensorQueue, &receivedMsg, NULL, osWaitForever);

if (status == osOK) {

if (receivedMsg.sensor_type == 1) {

printf("Received temperature data: %.2f\n", receivedMsg.value);

// 处理温度数据,比如判断是否需要调节空调温度

if (receivedMsg.value > 26.0) {

// 发送控制指令给空调开启制冷

printf("Sending command to AC to cool down...\n");

}

}

} else {

printf("Failed to receive message from queue!\n");

// 处理接收失败的情况,比如记录日志或进行其他操作

}

}

}

        在上述代码中,createMessageQueue函数负责创建一个名为SensorQueue的消息队列,队列长度为 10,每个消息的大小为SensorMessage结构体的大小。sensorTask函数模拟传感器任务,每隔 1 秒生成一个温度数据消息,并通过osMessageQueuePut函数将其发送到消息队列中。dataProcessingTask函数则作为数据处理任务,通过osMessageQueueGet函数从消息队列中获取消息,当获取到温度传感器的消息时,打印出温度数据,并根据温度值判断是否需要发送控制指令给空调 。

(三)运行结果分析

        当我们运行上述案例代码后,会发现dataProcessingTask任务能够准确地接收到sensorTask任务发送的温度数据消息。通过串口输出或日志记录,我们可以看到类似如下的输出信息:

Received temperature data: 25.50

Received temperature data: 25.50

Received temperature data: 25.50

        这表明消息队列成功地实现了任务间的通信。sensorTask任务能够将模拟的温度数据消息顺利地发送到消息队列中,而dataProcessingTask任务也能够及时地从队列中获取这些消息,并进行相应的处理和打印。如果我们在代码中添加更多的逻辑,比如根据温度数据控制空调的开关,也能够正常工作。这充分验证了消息队列在不同任务之间传递数据的可靠性和高效性,确保了智能家居环境监测系统中各个模块之间的协同工作 。

七、总结与展望

        在物联网这片充满无限可能的领域中,LiteOS 消息队列凭借其独特的优势,成为了众多开发者构建高效、可靠物联网应用的得力助手。通过本文的深入探讨,我们对 LiteOS 消息队列有了全面而清晰的认识 。

        从概念上看,消息队列作为任务间通信的关键数据结构,就像是物联网世界中的 “信息桥梁”,实现了任务和中断之间不固定长度消息的传递,其先进先出、异步读写和超时机制等特性,确保了数据传输的有序性、高效性和灵活性 。

        在原理方面,无论是创建队列时的内存开辟与初始化,还是写队列、读队列时对消息节点的精准操作,以及删除队列时对资源的合理释放,每一个步骤都经过了精心设计,背后蕴含着严谨的逻辑和高效的算法,为消息队列的稳定运行提供了坚实的保障 。

        LiteOS 提供的一系列 API 函数,如osMessageQueueNew、osMessageQueuePut、osMessageQueueGet和osMessageQueueDelete,为开发者提供了便捷、高效的操作接口。通过这些函数,开发者可以轻松地创建、发送、接收和删除消息队列,就像使用一套精密的工具,能够根据自己的需求构建出各种复杂而强大的物联网应用场景 。

        在智能路灯系统和多线程数据交换等实际应用场景中,LiteOS 消息队列展现出了强大的实力。它不仅能够实现不同设备和模块之间的高效通信,还能有效地协调多个任务的协同工作,大大提高了系统的性能和稳定性,让物联网设备的功能更加智能、完善 。

        随着物联网技术的不断发展,万物互联的时代正在加速到来。在未来,物联网设备的数量将呈现爆发式增长,设备之间的通信和协作将变得更加频繁和复杂。LiteOS 消息队列作为物联网操作系统中的核心组件,必将迎来更广阔的发展空间 。

        我们可以期待,在智能家居领域,LiteOS 消息队列将进一步优化不同智能设备之间的交互,实现更加智能化的家居控制体验。比如,当用户回到家时,智能门锁检测到用户身份后,通过消息队列快速通知智能灯光系统自动亮起,同时智能空调也根据用户的习惯自动调节到合适的温度,整个过程无缝衔接,为用户提供极致的便捷 。

        在工业物联网中,LiteOS 消息队列将助力实现更高效的生产流程管理。生产线上的各种传感器和设备通过消息队列实时传递数据,生产管理人员可以根据这些数据及时调整生产策略,提高生产效率,降低生产成本,推动工业生产向智能化、自动化方向迈进 。

        对于广大开发者而言,深入学习和掌握 LiteOS 消息队列的知识和技能,不仅能够提升自己在物联网领域的开发能力,还能为未来的职业发展打下坚实的基础。无论是参与开源项目,还是进行独立的物联网创新开发,LiteOS 消息队列都将是开发者手中的有力武器 。

        希望本文能够为大家打开 LiteOS 消息队列的大门,激发大家探索物联网世界的热情。让我们一起在物联网的浪潮中,利用 LiteOS 消息队列的强大功能,创造出更多精彩的应用,为推动物联网技术的发展贡献自己的力量!如果你在学习和实践过程中有任何问题或心得,欢迎随时在评论区留言交流,让我们共同进步 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大雨淅淅编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值