unix/linux下几种常见的IO模型

Unix/Linux 编程:网络编程之 IO模型 一、阻塞IO(blocking) 主线程阻塞等待用户连接请求(accept阻塞)。等请求达到以后再调用(recv)系统调用接收数据,这时候又阻塞等待。 等内核将数据接受好并从内核空间拷贝到用户空间后,系统调用返回,表明数据读取完毕。 这个操作在等待IO的时候会将主线程挂起而不能执行其他操作,浪费了大量服务器性能。 二、多线程(blocking+multithread) 由于accept系统调用会对每个客户端的连接都返回一个fd,因此可以利用多线程的方法。主线程只负责accept处理连接请求。 阅读详情

unix/linux下几种常见的I/O模型:

(以下图片均引用自UNP一书)

阻塞式I/O:

顾名思义就是当进行I/O时,数据还没有准备好,就会阻塞在I/O上。
这里写图片描述
下面配合代码进行讲解:

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>

#define SERV_PORT 8000

int main(void)
{
  struct sockaddr_in serv_addr, clie_addr;
  socklen_t clie_addr_len;
  int sockfd;
  char buf[BUFSIZ];
  char str[INET_ADDRSTRLEN];
  int i, n;

  sockfd = socket(AF_INET, SOCK_DGRAM, 0);

  bzero(&serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  serv_addr.sin_port = htons(SERV_PORT);

  bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

  printf("Accepting connections ...\n");
  while (1)
  {
      clie_addr_len = sizeof(clie_addr);
      //当客户端没发送数据时,会一直阻塞
      n = recvfrom(sockfd, buf, BUFSIZ,0, (struct sockaddr *)&clie_addr, &clie_addr_len);
      if (n == -1)
        perror("recvfrom error");

      printf("received from %s at PORT %d\n",
            inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),
            ntohs(clie_addr.sin_port));
      n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&clie_addr, sizeof(clie_addr));
      if (n == -1)
        perror("sendto error");
  }
  close(sockfd);
  return 0;
}

当运行到recvfrom时,如果客户端的数据还未发过来,则程序就阻塞在recvfrom上,等数据到了之后内核将数据拷贝给用户的缓冲区中或者因为某些原因调用失败了才会返回,中间的过程是一直阻塞在recvform函数上的。这种I/O方式就称为阻塞式I/O模型。

非阻塞I/O模型:

这里写图片描述

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#define SERV_PORT 8000

int main(void)
{
  struct sockaddr_in serv_addr, clie_addr;
  socklen_t clie_addr_len;
  int sockfd;
  char buf[BUFSIZ];
  char str[INET_ADDRSTRLEN];
  int i, n;

  sockfd = socket(AF_INET, SOCK_DGRAM, 0);

  bzero(&serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  serv_addr.sin_port = htons(SERV_PORT);

  bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

  //设置非阻塞
  int flag = fcntl(sockfd, F_GETFL);
  flag |= O_NONBLOCK;
  fcntl(sockfd, F_SETFL, flag);
  printf("Accepting connections ...\n");
  while (1)
  {
      clie_addr_len = sizeof(clie_addr);
      //由于我们对sockfd文件描述符设置了非阻塞,所以recvfrom调用会一直返回-1并设置errno为EAGAIN直到接收到数据
      n = recvfrom(sockfd, buf, BUFSIZ,0, (struct sockaddr *)&clie_addr, &clie_addr_len);
      if (n == -1 && errno == EAGAIN)
        perror("recvfrom error");

      printf("received from %s at PORT %d\n",
            inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),
            ntohs(clie_addr.sin_port));
      n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&clie_addr, sizeof(clie_addr));
      if (n == -1)
        perror("sendto error");
  }
  close(sockfd);
  return 0;
}

设置了文件描述符为非阻塞态之后,recvfrom就不会等待数据到达了,而是直接返回。所以当我们采用非阻塞I/O模型时,需要轮询内核,查看数据是否准备就绪。这样的做法会耗费大量的cpu资源,因为每次都要在内核和用户态之间切换。

I/O复用模型:

select、poll、epoll都属于I/O复用模型。当系统调用这几个系统调用时,会阻塞在这几个系统调用上,但不是阻塞在真正的I/O系统调用上。这样做的好处是可以一次性监控多个文件描述符,而不用像阻塞I/O模型那样每次只处理一个文件描述符。
这里写图片描述
这里只贴出select的server端代码以供讲解:

#include <iostream>
using namespace std;
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
    int i, max_i, max_fd, listen_fd,
    conn_fd, sock_fd;
    //用一个client数组专门来存放就绪的描述符
    int nready, client[FD_SETSIZE];
    ssize_t n;
    fd_set rset, allset;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    socklen_t cliaddr_len;
    struct sockaddr_in cliaddr, servaddr;


    listen_fd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listen_fd, 20);

    //记录监控的最大的文件描述符
    max_fd = listen_fd;
    //max_i用来记录client数组的有效下标
    max_i = -1;

    //初始化client数组
    for(i = 0; i < FD_SETSIZE; i++)
    {
        client[i] = -1;
    }

    //初始化文件描述符集合
    FD_ZERO(&allset);
    FD_SET(listen_fd, &allset);

    while(1)
    {
        //allset用来存放需要监听的所有文件描述符集合,而rset是指就绪的文件描述符集合
        rset = allset;
       //阻塞等待可读的文件描述符,如果有,则立即返回,否则阻塞
        nready = select(max_fd + 1, &rset, NULL, NULL, NULL);
        if(nready < 0)
        {
            perror("select error");
            exit(1);
        }
        //如果建立socket连接的描述符处于就绪
        if(FD_ISSET(listen_fd, &rset))
        {
            cliaddr_len = sizeof(cliaddr);
            conn_fd = accept(listen_fd, (struct sockaddr *)&cliaddr, &cliaddr_len);
            printf("received from %s at PORT %d\n",
                    inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                    ntohs(cliaddr.sin_port));
            for(i = 0; i < FD_SETSIZE; i++)
            {
                if(client[i] < 0)
                {
                    client[i] = conn_fd;
                    break;
                }
            }
            if(i == FD_SETSIZE)
            {
                fputs("too many clients\n", stderr);
                exit(1);
            }
            //将新的描述符加入监听的文件描述符集合中
            FD_SET(conn_fd, &allset);
            //更新最大的文件描述符和最大下标
            if(conn_fd > max_fd)
            {
                max_fd = conn_fd;
            }
            if(i > max_i)
            {
                max_i = i;
            }
            //如果没有就绪的文件描述符就退出本次循环,重新调用select监听
            if(--nready == 0)
            {
                continue;
            }
        }

        //轮询找到就绪的文件描述符
        for(i = 0; i <= max_i; i++)
        {
            if((sock_fd = client[i]) < 0)
            {
                continue;
            }
            if(FD_ISSET(sock_fd, &rset))
            {
                if((n = read(sock_fd, buf, MAXLINE)) == 0)
                {
                    close(sock_fd);
                    FD_CLR(sock_fd, &rset);
                    client[i] = -1;
                }
                else
                {
                    int j;
                    for(j = 0; j < n; j++)
                        buf[j] = toupper(buf[j]);
                    write(sock_fd, buf, n);
                }
                if(--nready == 0)
                {
                    break;
                }
            }
        }
    }
    close(listen_fd);
    return 0;
}

可以看到,select一次可以处理多个就绪的文件描述符,I/O复用模型就是这样的一个特点。效率方面比前几种都要好的多。而epoll则是poll的加强版,select和poll都是采用轮询查看哪个文件描述符就绪,而epoll是采用的回调机制来激活文件描述符,将就绪的文件描述符加入链表中,返回时,就知道了具体是哪些文件描述符处于就绪中。

信号驱动式I/O模型:

信号驱动这种方式的意思就是我们事先调用sigaction函数捕捉SIGIO信号并定义处理函数,等到数据已经准备就绪时,内核就发送SIGIO信号,当前进程就可以执行SIGIO处理函数了,在处理函数中调用recvfrom读取数据即可。
这种I/O模型的优点是当进程调用recvfrom时,数据必定已经可以读取了,因为当内核发送SIGIO信号时,数据已经准备就绪了。所以我们可以执行其它代码,而不用阻塞在某一处了。
这里写图片描述

异步I/O模型:

前面的I/O模型都是需要我们在读写事件就绪后自己负责进行读写,而异步I/O则不需要,是内核替我们做了这些事,最后只需要当I/O完成时才通知我们。注意它和信号驱动式I/O的不同,信号驱动式I/O是通知我们何时可以进行I/O,而异步I/O是通知我们何时完成了I/O。
这里写图片描述

最后贴上阻塞式I/O和非阻塞式I/O用于测试的client端代码:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <ctype.h>

#define SERV_PORT 8000

int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr;
    int sockfd, n;
    char buf[BUFSIZ];

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);

    while (fgets(buf, BUFSIZ, stdin) != NULL) {
        n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
        if (n == -1)
            perror("sendto error");

        n = recvfrom(sockfd, buf, BUFSIZ, 0, NULL, 0);         //NULL:不关心对端信息
        if (n == -1)
            perror("recvfrom error");

        write(STDOUT_FILENO, buf, n);
    }

    close(sockfd);

    return 0;
}

测试I/O复用的话,直接使用nc 127.0.0.1 6666来模拟client端即可。

【论文复现】HiDDeN:Hiding Data With Deep Networks (标签)是 float 类型,但代码中传入的是 long 类型(整数标签)。函数参数解析方式变化,导致无法正确保存生成的图像结果。这是因为新版torchvision的。使用预训练模型进行隐写和提取测试。HiDDeN支持多种噪声层组合。打印变量的类型,分别是。 阅读详情

相关推荐

基于JavaFX的Http Server模拟器

源码地址 GitHub - DayRain/http-server-simulator: http server 模拟器 功能描述 HttpServer模拟器,通过简单的配置即可模拟第三方服务端,用于接收和响应HTTP请求。 支持同时模拟多个服务、动态添加接口、动态配置返回值。 使用说明 运行 exe运行 编译代码后,点击exe直接运行 代码运行 下载好代码后,运行主类ApplicationStarter即可 新建服务 主界面如下 点击 文件-新建 创建新的服务 ...

小楼夜听雨的博客 2534

Linux中5种IO模型

同步/异步,阻塞/非阻塞,Linux中5种IO模型,BIO、NIO、AIO

weixin_45819295的博客 2804

Prometheus-2.53.1安装

Prometheus+Grafana安装

qlj062127的博客 1788

Linux的五个I/O模型

前面的 fcntl()函数解决了文件的共享问题, 接下来该处理 I/O 复用的情况了。 总的来说, I/O 处理的模型有 5 种。  1.阻塞 I/O 模型: 在这种模型下, 若所调用的 I/O 函数没有完成相关的功能, 则会使进程挂起, 直到相关数据到达才会返回。 如常见对管道设备、 终端设备和网络设备进行读写时经常会出现这种情况。 2.非阻塞 I/O 模型: 在这种模型下, ...

QtHalcon 5476

深入解析以太网接口中的BOB Smith电路设计

本文深入解析以太网接口中的BOB Smith电路设计,详细介绍了其在共模噪声抑制、浪涌防护和EMI优化中的关键作用。通过实际案例和设计要点,帮助工程师正确选择75Ω电阻和高压电容等元件,优化PCB布局,解决电压型与电流型PHY的设计差异,以及千兆与百兆以太网的特殊考量。

weixin_28339967的博客 687

告别两个电源模块!用UCD3138+四开关Buck-Boost,一个电路搞定电池充放电(附原理图分析)

本文详细介绍了采用UCD3138数字控制器和四开关Buck-Boost拓扑的双向DC/DC变换器设计,实现单一电路完成电池智能充放电管理。该方案显著降低BOM成本和PCB面积,提升系统效率至96.2%,适用于UPS、通信基站和储能系统,具有高效、紧凑和成本优势。

weixin_30256901的博客 397

Linux下五种IO模型

目录1、引入的概念1.1 用户空间和内核空间1.2 进程切换1.3 进程的阻塞 1.4文件描述符fd1.5 缓存IO2、Linux IO模型2.1 同步阻塞IO(blocking IO)2.1.1 同步阻塞情景描述:2.1.2 网络模型2.1.3 流程描述2.2 同步非阻塞IO(nonlocking IO)2.2.1 同步非阻塞情景描述2.2.2 网络模型2.2.3 流程描述2.3 IO多路复用(IO multiplexing)2.3.1 IO多路复用情景描述2.3.2 网络模型2.3.3 流程描述2.4

m0_52165864的博客 1719

PostgreSQL和PostGIS安装、导入shp或gdb格式数据至数据库、使用QGIS测试上传PstGIS数据库操作和代码

本文详细介绍了postgresql和postgis安装步骤,并且使用postgis测试上传shp和gdb格式的矢量数据至数据库,包括步骤和代码,并使用qgis连接postgis数据查看数据库里的数据,数据依旧和本地数据一样可查看可编辑,方便后续数据底座开发和服务。

weixin_46456488的博客 1246

Linux系统--五种IO模型

Linux中的五种IO模型主要包括阻塞IO、非阻塞IO、IO复用、信号驱动IO和异步IO。

积木的笔记 1447

使用VS Code创建第一个Flutter工程

你可以使用任意文本编辑器结合命令行工具来开发Flutter应用。然而,我们推荐使用本文介绍的编辑器插件以获取更好的开发体验。这些插件提供了代码补全、代码高亮、widget辅助编辑等功能,以及为项目的运行和调试提供支持。推荐的开发工具有VS Code、Android Studio、IntelliJ。本文将重点介绍如何使用VS Code进行Flutter应用开发。

2501_91373349的博客 2126

【51单片机仿真】基于51单片机设计的多功能电子时钟日历(实时时钟闹钟秒表温度检测)系统__资料编号5

该项目是基于单片机的多功能电子时钟系统,集成了时间显示、闹钟设置、秒表功能以及温度显示等功能。主要硬件包括1602液晶显示屏、DS1302时钟芯片、DS18B20温度传感器和蜂鸣器。系统通过按键进行时间和闹钟的设置,并实时显示当前时间和温度。

嵌入式阿齐Archie 2万+

Linux中的五种IO模型

Linux中有以下五种IO模型。一、同步阻塞IO(Blocking IO, BIO)二、同步非阻塞IO(Non-Blocking IO, NIO)三、IO多路复用 四、信号驱动 I/O 五、异步 I/O(Async IO, AIO)

m0_56602092的博客 728

mapbox进阶,dem数据转为等高面,加载转换后的等高面到地图

本文详细介绍如何基于mapbox-gl v3.*.* 版本,使用dem数据转为等高面,加载转换后的等高面到地图,亲测可用。希望能帮助到您。一起学习,加油!加油!

我哎GIS博客 407

Linux/Unix五种IO模型

五种IO模型 在《Unix网络编程》一书中提到了五种IO模型,分别是:阻塞IO、非阻塞IO、多路复用IO、信号驱动IO以及异步IO。也被称为Linux的五种网络IO模型。 由于服务器端一般都是使用的Linux操作系统,所以了解这几种io模型学习java网络io模型的基础。Java的IO模型与Unix的IO模型的对应关系(这个对应关系并不严格):java-BIO模型对应Unix阻塞IO, java-NIO对应IO多路复用,java-AIO对应异步IO。 ............

m0_45861545的博客 1823

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

【Linux C | I/O模型】Unix / Linux系统的5种IO模型 | 图文详解

本文图文详解Linux系统的五种IO模型,阻塞I/O模型、非阻塞I/O模型、复用式I/O模型、信号驱动式I/O模型、异步I/O模型

wkd_007的博客 2191

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

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

(通俗易懂)Unix/Linux上的五种IO模型

通俗易懂系列第二节,Unix/Linux上的五种IO模型

weixin_45634782的博客 984

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Linux/Unix常见IO模型

应用进程向内核发起 I/O 请求后不再会同步等待结果,而是会立即返回,通过轮询的方式获取请求结果。NIO 相比 BIO 虽然大幅提升了性能,但是轮询过程中大量的系统调用导致上下文切换开销很大。所以,单独使用非阻塞 I/O 时效率并不高,而且随着并发量的提升,非阻塞 I/O 会存在严重的性能浪费。

MrLee的博客 1109

Unix网络编程里面定义的几种IO模型

I/O(英语:Input/Output),即输入/输出,通常指数据在存储器(内部和外部)或其他周边设备之间的输入和输出,是信息处理系统(例如计算机)与外部世界(可能是人类或另一信息处理系统)之间的通信。输入是系统接收的信号或数据,输出则是从其发送的信号或数据。 Unix网络编程中的五种IO模型 blocking IO - 阻塞IO nonblocking IO - 非阻塞IO IO multiplexing - IO多路复用 asynchronous IO - 异步IO signal driven I.

githubcurry,985cs博士在读 520

Unix网络 IO 模型及 Linux 的 IO 多路复用模型详解

本文主要探讨的问题有以下两个: Unix 中的五种网络 IO 模型; Linux 中 IO 多路复用的实现。 一、基本概念 在介绍网络模型之前,先简单介绍一些基本概念。 1.文件描述符 fd 文件描述符(file descriptor,简称 fd)在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。...

zhipingxi的博客 421

5.2、Unix/Linux上的五种IO模型

5.2、Unix/Linux上的五种IO模型1.阻塞blocking2.非阻塞non-blocking(NIO)3.IO复用(IO_multiplexing)4.信号驱动(signal-driven)5.异步(asynchronous)①异步函数介绍 1.阻塞blocking 调用者调用了某个函数,等待这个函数返回,期间什么也不做,不停的去检查这个函数有没有返回,必 须等这个函数返回才能进行下一步动作。 2.非阻塞non-blocking(NIO) 非阻塞等待,每隔一段时间就去检测IO事件是否就绪。

z2812470857的博客 1074

Unix 网络 IO 模型及 Linux 的 IO 多路复用模型

本文主要探讨的问题有以下两个: Unix 中的五种网络 IO 模型; Linux 中 IO 多路复用的实现。 基本概念 在介绍网络模型之前,先简单介绍一些基本概念。 文件描述符 fd 文件描述符(file descriptor,简称 fd)在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向...

yangrendong的专栏 389

网络编程2: Unix/Linux下的五种IO模型

Unix/Linux下的五种IO模型(网络编程的角度)

yubaoaihh 299

unix和Linux的五种io模型,Unix的五种IO模型介绍

前言之前打算总结一下Java的BIO(IO),AIO,NIO,最后一步步深入,发现Unix(Linux)的IO模型需要提前掌握,所以先总结一下Unix的IO模型。概述Java IO 与 Unix IO 的关系(非严格对应)Unix网络编程中介绍了五种IO模型,分别是:阻塞IO、非阻塞IO、IO多路复用、信号驱动IO、异步IO。Java的IO模型与Unix的IO模型的对应关系如下所示(这个对应关系并...

weixin_28973627的博客 276

Unix网络编程之5种IO模型

1. Unix/Linux操作系统简述 Unix操作与Linux系统结构图解 (引用计算机操作系统书籍) 概要说明 用户空间: 姑且将上述用户级别或者是unix编程的应用程序的部分称为用户空间,我们可以通过启动进程来调用内核来完成从硬件设备读取或写入等操作 系统内核: 是直接与计算机硬件打交道的应用程序级别,在计算机相关的书籍中也称为操作系统,可以通过操作系统级别提供的一些组件来帮助...

疾风先生的博客 687

【Linux网络】五种IO模型与非阻塞IO

本文聚焦操作系统与 Linux 网络编程核心 IO 体系,从 IO 最底层的本质概念入手,拆解 IO 执行的两大核心步骤,解释 IO 运行低效的根本原因,并给出优化 IO 效率的核心思路。为了降低抽象概念的理解难度,全文采用钓鱼生活化案例类比业界通用的五种经典 IO 模型,依次讲解阻塞 IO、非阻塞 IO、信号驱动 IO、IO 多路复用、异步 IO 的运行逻辑,同时精准界定同步 IO 与异步 IO的判定标准,搭配可直接编译运行的 C 语言代码完成信号驱动 IO、POSIX 异步 IO 的实操演示。

艾莉丝努力练剑的博客,感谢大家一直以来的对艾莉丝的支持和鼓励,uu们如果对C/C++方向感兴趣的话,可以通过艾莉丝主页的微信名片加艾莉丝的好友,和努力练剑的艾莉丝一起加油! 2697

常见Linux IO模型分析

LINUX常见IO模型介绍 UNIX中常见IO模型有五种:阻塞 IO、非阻塞IO、多路复用IO、异步IO、信号驱动IO。其中阻塞IO、非阻塞IO、多路复用IO、信号驱动IO都属于同步IO。 同步IO和异步IO 同步IO:应用程序主动向内核查询是否有可用数据,如果有自己负责把数据从内核copy到用户空间。 异步IO:应用程序向内核发起读数据请求需要:(1)告诉内核数据存放位置(2

lovelinx的专栏 526
上一篇: atom安装markdown-preview-enhanced和markdown-pdf插件
下一篇: epoll比select和poll高效的原因
UKey_
博客等级 码龄10年 258粉丝 186原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值