Swoole:心跳检测

Swoole长连接,心跳包Heartbeat检测 Swoole长连接,心跳包Heartbeat检测 首先:长连接都是很消耗资源的。所以,需要有个机制来检测客户端是否还活着 Or 存在,不存在就断开连接,减少资源消耗。 众所周知,Tcp连接需要3次握手跟4次挥手。不管是服务器主动发起断开连接,还是客户端发起断开连接,都要经历完整的四次挥手阶段。最后由系统回收客户端的文件描述符fd(后面看代码就能看出fd标识符文件是啥)。 为什么要回收描述符呢 还是... 阅读详情


前言:

心跳:心脏还在跳动,说明还有生命迹象,还活着,还活着就表示还可以继续工作,生命不止,工作不息。

WHY

为什么需要心跳检测?这个小孩没娘,说来话长,long long ago ,TCP协议的诞生,惊天地泣鬼神,改变了世界。

建立连接:三次握手

断开连接:四次挥手 ‍♂️

趣解:

寓言两则:(client、server的精彩对话)。此故事纯属虚构,如有雷同,不好意思。

人物一:小C(client 客户端)

人物二:大S (server 服务端)

场景一:初识(建立连接 ——三次握手)

小C:很高兴认识你,我想和你处对象。

大S:看你挺有诚意的,我同意

小C:收到对方的回信,欣喜若狂,赶紧再说:“你等着我啊,马上咱们就*****(美好的生活)”;

(第一次握手:客户端发送信号,我准备发送数据,客户端进入准备发送状态)

(第二次握手:服务端收到信号,并给客户端回信,服务端进入准备接收状态)

(第三次握手:客户端收到回信,进入已连接状态,并给服务端确认,服务端收到,两端都进入建立连接状态)

场景二:依依惜别 (断开连接——四次挥手)

小C:我不想和你说话了,

大S:你是在开玩笑吧,

大S:(看到小C认真的面容,知道这不是玩笑),好吧

小C:你同意就好,再见。(看这大S伤心而去,驻足一段时间,自己也离开了)

(第一次挥手:客户端发出关闭请求,此时不在发送数据。)

(第二次挥手:服务端收到请求并确认,服务端进入等待关闭状态——把自己想说的话,说完)

(第三次挥手:服务端向客户端发送确认关闭信号,此时不再发送数据,进入最后确认关闭状态)

(第四次挥手:客户端收到关闭请求,向服务端恢复信息,服务端关闭。客户端会在稍后关闭)

详情查看:https://blog.youkuaiyun.com/qq_37837134/article/details/79738329

上面简单了解了TCP协议,那么有没有这样一种场景呢:只建立连接,而不断开连接

答案是肯定的,在复杂的网络环境中,这种情况必然存在。例如:夏天用电高峰期负载高,断电了;我的家里刚通网,还是不很好,时常在高潮的时候莫名其妙的断网……

总之,无缘无故的不辞而别,总是存在的,没有那么完美的爱情。

what

心跳机制:客户端定时发送 ping,服务端回复 pong。

ping、 pong 无特殊意义,就是简单的通讯传输,朋友之间常联络,才不会生疏。时间久了,服务端就要把你踢了,人的精力总是有限的,有的时候,整理自己的心情,会删除一些无用的通讯录。

服务端的资源也是有限的,那么就要把没用的fd 收回,重复利用。

fd 是什么?

fd (file descriptor) 文件描述符

一切皆文件,fd 就相当于索引,拿着这个标示去进行一系列操作。

swoole 中

$fd是TCP客户端连接的标识符,在Server实例中是唯一的,在多个进程内不会重复
fd 是一个自增数字,范围是1 ~ 1600万,fd超过1600万后会自动从1开始进行复用
$fd是复用的,当连接关闭后fd会被新进入的连接复用
正在维持的TCP连接fd不会被复用
不要担心,同一台服务1600万的fd 不够自己用,因为服务器承受不了1600万的连接,如果真有那么多连接,肯定也不是单机了。

How

swoole 实际上已经实现了心跳检测机制,咱们只需要去开启配置就行了。如此方便,不得不赞,峰哥威武!

heartbeat_check_interval
 
心跳检测 每隔多少秒,遍历一遍所有的连接
 
heartbeat_idle_time
 
心跳检测 最大闲置时间,超时触发close并关闭  默认为heartbeat_check_interval的2倍,两倍是容错机制,多一点是网络延迟的弥补
官方文档:https://wiki.swoole.com/wiki/page/283.html

当然,心跳机制也可以自己实现,定期轮询fd,是否在线,记录最近回话时间,剔除超时的连接。

伪代码

webSocket Server:

<?php
/**
 * 心跳检测机制 测试
 * Created by PhpStorm.
 * User: 奔跑吧笨笨
 * Date: 2019/8/1
 * Time: 3:04 PM
 */
 
include_once './class/UsersBind.php';
 
//创建websocket服务器对象,监听0.0.0.0:9876端口
$ws = new swoole_websocket_server("0.0.0.0", 9876);
 
$ws->set([
    'heartbeat_check_interval' => 30,       //心跳检测 每隔多少秒,遍历一遍所有的连接
    'heartbeat_idle_time' => 65,           //心跳检测 最大闲置时间,超时触发close并关闭  默认为heartbeat_check_interval的2倍,两倍是容错机制,多一点是网络延迟的弥补
]);
 
//监听WebSocket连接打开事件
$ws->on('open', function ($ws, $request) {
    var_dump($request->fd, $request->get, $request->server);
    $ws->push($request->fd, "hello, welcome\n");
 
    //一、建立连接 uid、fd 关系绑定
    $bindObj = new UsersBind();
    $bindObj->userIdBind(1,$request->fd);
    $bindObj->fdBind(1,$request->fd);
});
 
//监听WebSocket消息事件
$ws->on('message', function ($ws, $frame) {
    //二、连接关闭 通过uid,获取fd
    $bindObj = new UsersBind();
    $fd = $bindObj->getBindFd(2);
 
    echo "Message: {$frame->data}\n";
    $ws->push($frame->fd, "server: {$frame->data}");
});
 
//监听WebSocket连接关闭事件
$ws->on('close', function ($ws, $fd) {
    $close_str = "client-{$fd} is closed\n";
    echo $close_str;
    file_put_contents('./a.log',$close_str,FILE_APPEND);
 
    //三、连接关闭 uid、fd 关系解除
    $bindObj = new UsersBind();
    $bindObj->unbindFd(1,$fd);
});
 
$ws->start();
客户端 client js:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ws连接</title>
</head>
<body>
 
</body>
<script>
    var wsServer = 'ws://127.0.0.1:9876';
    var websocket = new WebSocket(wsServer);
    websocket.onopen = function (evt) {
        console.log("Connected to WebSocket server.");
    };
 
    websocket.onclose = function (evt) {
        console.log("Disconnected");
    };
 
    websocket.onmessage = function (evt) {
        console.log('Retrieved data from server: ' + evt.data);
        //websocket.send('Lalal');
    };
 
    websocket.onerror = function (evt, e) {
        console.log('Error occured: ' + evt.data);
    };
</script>
</html>

fd 结合业务user id 用户关系映射:

场景:

多台服务器
同一用户多个客户端可同时登录,消息共享
<?php
 
/**
 * Created by PhpStorm.
 * User: runBaby
 * Date: 2019/8/3
 * Time: 5:55 PM
 */
class UsersBind
{
 
 
    private $redisObj;
    private $fieldPrefix;
    private $serverNode;
    private $redisTable;
 
    private $clientNode = ['APP', 'PC'];
 
    const REDIS_HOST = '127.0.0.1';
    const REDIS_PORT = 6379;
    const REDIS_TIMEOUT = 2;
 
    public function __construct($redisTable = 'ws', $fieldPrefix = 'bind', $serverNode = 0, $client = 0)
    {
        $this->redisObj = new Redis();
        $this->redisObj->connect(self::REDIS_HOST, self::REDIS_PORT, self::REDIS_TIMEOUT);
 
        $this->redisTable = $redisTable;
        $this->serverNode = $serverNode;
        $this->clientNode = $this->clientNode[$client];
        $this->fieldPrefix = $fieldPrefix;
 
    }
 
 
    /**
     * Explain: set field prefix
     * User: runBaby
     * Date: 2019/8/3
     * Time: 6:15 PM
     * @return string
     */
    private function setFieldPrefix($type = 0)
    {
        $keyPrefix = $this->fieldPrefix . ':server:' . $this->serverNode . ':client:' . $this->clientNode . ':';
        if ($type === 1) {
            $keyPrefix .= 'fd:';
        } else {
            $keyPrefix .= 'uid:';
        }
 
 
        return $keyPrefix;
    }
 
 
    /**
     * Explain: redis key
     * @param $uid
     * User: runBaby
     * Date: 2019/8/3
     * Time: 6:26 PM
     * @return string
     */
    private function setRedisField($id, $type = 0)
    {
        $fieldPrefix = $this->setFieldPrefix($type);
        $key = $fieldPrefix . $id;
 
 
        return $key;
    }
 
 
    /**
     * Explain: user id bind fd
     * @param $uid
     * @param $fd
     * User: runBaby
     * Date: 2019/8/3
     * Time: 6:23 PM
     * @return int
     */
    public function userIdBind($uid, $fd)
    {
        $field = $this->setRedisField($uid);
        $result = $this->redisObj->hSet($this->redisTable, $field, $fd);
 
        return $result;
    }
 
 
    /**
     * Explain: fd bind user id
     * @param $uid
     * @param $fd
     * User: runBaby
     * Date: 2019/8/4
     * Time: 9:08 PM
     * @return int
     */
    public function fdBind($uid, $fd)
    {
        $field = $this->setRedisField($fd, 1);
        $result = $this->redisObj->hSet($this->redisTable, $field, $uid);
 
 
        return $result;
    }
 
 
    /**
     * Explain: Two-way binding
     * @param $uid
     * @param $fd
     * User: runBaby
     * Date: 2019/8/4
     * Time: 9:32 PM
     * @return bool
     */
    public function setBindId($uid, $fd)
    {
        $result_uid = $this->userIdBind($uid, $fd);
        $result_fd = $this->fdBind($uid, $fd);
 
 
        if($result_uid && $result_fd) {
            return true;
        } else {
            return false;
        }
    }
 
 
    /**
     * Explain: get bind fd
     * @param $uid
     * User:runBaby
     * Date: 2019/8/3
     * Time: 6:28 PM
     * @return string
     */
    public function getBindFd($uid)
    {
        $field = $this->setRedisField($uid);
        $data = $this->redisObj->hGet($this->redisTable, $field);
 
 
        return $data;
    }
 
 
    /**
     * Explain: get bind user id
     * @param $fd
     * User:runBaby
     * Date: 2019/8/3
     * Time: 6:28 PM
     * @return string
     */
    public function getBindUserId($fd)
    {
        $field = $this->setRedisField($fd, 1);
        $data = $this->redisObj->hGet($this->redisTable, $field);
 
 
        return $data;
    }
 
 
    /**
     * Explain: unbinds userId and  fd
     * @param $uid
     * User: runBaby
     * Date: 2019/8/3
     * Time: 6:38 PM
     * @return bool
     */
    public function unbindFd($uid, $fd)
    {
        //unbind userId
        $field_uid = $this->setRedisField($uid);
        $result_uid = $this->redisObj->hDel($this->redisTable, $field_uid);
        //unbind fd
        $field_fd = $this->setRedisField($fd, 1);
        $result_fd = $this->redisObj->hDel($this->redisTable, $field_fd);
 
 
        if($result_uid && $result_fd) {
            return true;
        } else {
            return false;
        }
    }
 
 
}
 

我为人人,人人为我,美美与共,天下大同。
————————————————
版权声明:本文为优快云博主「奔跑吧笨笨」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/qq_37837134/article/details/98474862

Swoole通讯正确配置步骤及走过的哪些坑,亲测解决www.youngprogram.com原创。 因为项目需要,linux服务器需要配置Swoole通讯,经各种调试和排查,终于圆满解决, 分享记录如下: 项目准备: A:完成swoole通讯配置文件的配置(注意自己的目录)文件名:socket.config.php,代码如下: <?php /** * socket server配置文件,重启后生效 */ // 开发模式开关 define('SOCKET_SERVER_DEBUG', false); // 设置服务端IP define('SOCKET_SERVER_IP', 'l 阅读详情

相关推荐

swoole入门教程03-Timer定时器、心跳检测及Task进阶实例:mysql连接池

环境说明: 系统:Ubuntu14.04 (安装教程包括CentOS6.5) PHP版本:PHP-5.5.10 swoole版本:1.7.7-stable Github地址:https://github.com/LinkedDestiny/swoole-doc 1.Timer定时器 在实际应用中,往往会遇到需要每隔一段时间重复做一件事,比如心跳检测、订阅消息、数据库备份等

ldy3243942的专栏 1万+

swoole的心跳检测

  swoole提供了一个心跳的功能,很多朋友感到困惑。   心跳是什么? 顾名思义,心跳是判断一个事物生还是死的一个标准,在swoole里,心跳是指用来判断一个连接是正常还是断开的。 从TCP协议说起 我们都知道一个五元组标识一个网络连接,创建一个连接有三次握手,而断开一个连接有四次挥手。不管是服务器还是客户端 发起连接的关闭,都会完整的走完四次挥手的过程,这样,一切很完美,系统回...

土豆科技的博客 8195

mongoDB 数据莫名其妙的没了

问题 之前没有接触过mongoDB,然后接手一个项目使用的是MongoDB,我就跟着网上的教程搭建一个MongoDB的服务,并运行起来。 一开始没有啥问题,成功的把服务与MongoDB都启动以来,运行也没有问题。但是过了几天,我再次打开时发现,以前的数据神奇的消失了。我还在想,是有什么定时清除的么,还是什么原因导致崩了然后数据就被清理了。 查找原因 第一次遇到我没有在意,重启了一下MongoDB,...

技术熊的博客小窝 1万+

php 心跳检测,Swoole 实例四(心跳检测)

服务器端 server.php

weixin_42525632的博客 1250

手把手教你用scRNA-seq和scTCR-seq分析CD8⁺ T细胞在免疫治疗中的动态变化

本文详细介绍了如何整合单细胞RNA测序(scRNA-seq)与单细胞TCR测序(scTCR-seq)技术,深入解析CD8⁺ T细胞在癌症免疫治疗中的动态变化。通过从数据预处理、细胞亚群鉴定到TCR克隆追踪的完整分析流程,揭示了T细胞的功能状态转变与克隆演化规律,为理解免疫应答机制和开发精准治疗策略提供了关键方法学指导。

p8q9r0的博客 567

Python实战:用MOOTDX库轻松抓取股票分时数据并绘制专业图表(附完整代码)

本文详细介绍了如何使用Python的MOOTDX库获取股票分时数据,并通过Pandas进行数据清洗与VWAP计算,最后利用Matplotlib绘制包含价格线、均线和成交量的专业分时图。文章提供了从环境搭建、数据获取到可视化呈现的完整代码,帮助读者快速构建本地化的股票分时数据分析工具。

weixin_29041195的博客 473

Swoole 实践篇之结合 WebSocket 实现心跳检测机制

这里实现的心跳检测机制是一个基础版的,心跳包的主要作用是用于检测用户端是否存活,有助于我们及时判断用户端是否存在断线的问题。在我之前开发过的项目中,有一个基于物联网在线直播抓娃娃的项目,其中就有需要实时监控设备在线状态的需求,该需求就是使用心跳包来实现的。实际上心跳检测技术,应用更广泛的是实时通信、或设备管理的场景偏多。

A码农先森的专栏 1427

CMOS反相器版图设计全解析:PMOS/NMOS布局与VDD/VSS连接门道

版图设计是芯片从电路原理图迈向物理实现的必经桥梁,而CMOS反相器作为最基础的数字单元,恰好是理解这一过程的最佳切入点。在版图绘制中,PMOS与NMOS因导电类型和迁移率差异,需要在N阱与P衬底上分别规划,并呈现出不同的有源区尺寸。真正决定版图好坏的关键,往往在于VDD/VSS的完整连接——不仅要接通源漏,还要通过阱接触和衬底接触固定阱与衬底电位,防止寄生PN结正偏和闩锁效应。合理布局电源地线轨道、优化接触孔数量,能显著降低寄生电阻与电迁移风险。这些原理不仅适用于反相器,也是所有CMOS数字标准单元版图设计

weixin_29159441的博客 302

socket心跳检测_Swoole学习笔记六:搭建WebSocket长连接 之 服务端实现强制心跳检测...

0、前言前面我们已经学过如何实现客户端心跳重连,不过如果我们服务端什么都依赖于客户端,那是不可取、也不合理的。假设:按客户端心跳重连操作,一直发包不成功,服务端就会一直等待该连接的请求,如果这时候服务器设置连接自动关闭时间为10分钟,那么我们服务端是不是一定要等服务器自动回收连接呢?按我们的想法,应该是服务端定时1分钟检测一次所有用户的心跳重连状态,只要重连次数超过3次,则自动关闭该连接。1、注意...

weixin_39998273的博客 721

sdio wifi 移植

介绍 简单来说,sdio起源自sd标准,顾名思义就是sd的IO接口(interface)的意思,更具体的说明,sd本来是记忆卡的标准,但是现在也可以把SD卡拿来插上一些外围设备接口使用,这个就是就叫做sdio。 而且sdio本身是一种相当单纯的技术,通过sd的IO引脚来连接外部外围,并且通过sd的IO数据接位与外围传输数据,而且sd协会也推出很完整的sdio stack 驱动程序,使得sdio外围得开发与应用变得非常热门 这里我们要移植的是RTL8822cs的sdio WiFi 模块,将其移植到rk3

Enosji的博客 9610

《Android系统开发中高级定制专栏导读》

欢迎来到【Android 中高级系统定制】专栏!在这里,将深入探讨Android系统开发与定制的方方面面,涵盖从权限管理、驱动开发到系统应用配置等多个领域。

我其实什么都不会 1579

socket心跳检测_Swoole学习笔记五:搭建WebSocket长连接 之 客户端实现心跳重连

0、前言我们先来想一个场景,我们进入index.php客户端页面这时候是正常上线了,也与服务端握手成功。突然,外面网线被人挖断了,3分钟后恢复网络,这时候我们的页面没有刷新,也没有接收到服务端的断开提示。这时候该怎么办?我们是需要刷新页面重新登录吗?答案当然是否,在产品思维里,每个客户端的用户都是大熊猫,都是懒的,所以你要他刷新页面,他宁愿不再使用这个程序。这时候我们需要在程序上想办法解决,先辈们...

weixin_39981632的博客 444

WPF用户控件间属性传递的艺术

本文详细介绍了WPF中父控件向子控件传递属性的方法。针对常见的绑定失效问题,重点讲解了使用RelativeSource绑定技术,通过指定AncestorType确保正确访问父控件属性。文章通过实例演示了从ParentModel传递属性到ChildUserControl依赖属性的完整实现过程,包括模型定义、XAML结构设计和代码实现。这种方法有效解决了数据上下文混淆问题,为WPF开发中的控件交互提供了可靠解决方案,有助于提升应用的灵活性和可维护性。

2501_90323865的博客 56

swoole实现Timer定时器、心跳检测及Task进阶实例:mysql连接池

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33923762的博客 333

Lammps纳米压痕接触面积—Matlab接触面积计算(两种方法)

Lammps纳米压痕接触面积计算

Rockyoung 5448

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

mysql timer控件实现实时读取_swoole实现Timer定时器、心跳检测及Task进阶实例:mysql连接池...

Table of Contents环境说明: 系统:Ubuntu14.04 (安装教程包括CentOS6.5)PHP版本:PHP-5.5.10swoole版本:1.7.7-stable1.Timer定时器在实际应用中,往往会遇到需要每隔一段时间重复做一件事,比如心跳检测、订阅消息、数据库备份等工作。通常,我们会借助PHP的time()以及相关函数自己实现一个定时器,或者使用crontab工具来实现...

weixin_42429120的博客 239

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

php mysql心跳包_swoole入门教程03-Timer定时器、心跳检测及Task进阶实例:mysql连接池...

环境说明: 系统:Ubuntu14.04 (安装教程包括CentOS6.5)PHP版本:PHP-5.5.10swoole版本:1.7.7-stable1.Timer定时器在实际应用中,往往会遇到需要每隔一段时间重复做一件事,比如心跳检测、订阅消息、数据库备份等工作。通常,我们会借助PHP的time()以及相关函数自己实现一个定时器,或者使用crontab工具来实现。但是,自定义的定时器容易出错,而...

weixin_33565558的博客 279

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

php心跳检测实时数据,心跳检测 · swoole手册 · 看云

# 心跳注意:长链接应用必须加心跳,否则链接可能由于长时间未通讯被路由节点强行断开。心跳作用主要有两个:1、客户端定时给服务端发送点数据,防止连接由于长时间没有通讯而被某些节点的防火墙关闭导致连接断开的情况。2、服务端可以通过心跳来判断客户端是否在线,如果客户端在规定时间内没有发来任何数据,就认为客户端下线。这样可以检测到客户端由于极端情况(断电、断网等)下线的事件。使用Timer定时器功能可以实...

weixin_42356892的博客 1121

tp6 swoole心跳

use Swoole\Timer; $this->serv->on('message', function ($serv, $frame) { $data = json_decode($frame->data, true); echo "Message: ".$data['period']. PHP_EOL; $this->serv->push($frame->fd, "serve

yang_ldgd的博客 515

Swoole TCP长链接

如何维持长链接

深漂小码哥 731

mysql心跳检测_Timer定时器、心跳检测及Task进阶实例:mysql连接池

> 环境说明: 系统:Ubuntu14.04 (安装教程包括CentOS6.5)> PHP版本:PHP-5.5.10> swoole版本:1.7.7-stable## **1.Timer定时器**在实际应用中,往往会遇到需要每隔一段时间重复做一件事,比如心跳检测、订阅消息、数据库备份等工作。通常,我们会借助PHP的time()以及相关函数自己实现一个定时器,或者使用crontab...

weixin_35161750的博客 873

TP5.1 + think-swoole 打造聊天室

搜遍全网都没找到正经的demo,琢磨了两三天,走了不少弯路才搞出来这个,弄完发现真是无敌简单,如果你也跟我一样在踩坑,可以参考一下。

花儿为何这样红 2583

swoole之简单入门和心跳检测

  Swoole虽然是标准的PHP扩展,实际上与普通的扩展不同。普通的扩展只是提供一个库函数。 而swoole扩展在运行后会接管PHP的控制权,进入事件循环。当IO事件发生后,swoole会自动回调指定的PHP函数。   Swoole感觉大大提高了php的生命力,而且这是国人开发的(用C编写的一个高性能)网络框架。   Swoole相对于传统的php框架是有很大不同的。 环境依赖: ...

weixin_34332905的博客 232

swoole 服务端heartbeat_check_interval心跳检测 客户端 swoole_timer_tick 发送心跳包 这里只是个demo参考

服务端&lt;?php class TimerServer { private $serv; public function __construct() { $this-&gt;serv = new swoole_server("0.0.0.0", 9501); $this-&gt;serv-&gt;set(array...

ljwy1234的博客 3013
上一篇: PHP设置时区
下一篇: Yii2框架中如何实现操作数据库某字段增加或减少一定数值?
firstcode666
博客等级 码龄5年 53粉丝 20原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值