基于C++的Qt网络编程——聊天客户端

一、实验题目 

网络聊天程序的设计与实现

二、实验目的 

了解Socket通信的原理,在此基础上编写一个聊天程序。

总体设计

1. 基本原理

socket通信原理是一种“打开——读/写——关闭”模式的实现,服务器和客户端各自维护一个“文件”,在建立连接打开后,可以向文件写入内容供对方读取或者读取对方的内容,通讯结束时关闭文件。

Socket在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口,供应用层调用实现进程在网络中的通信。

Socket保证了不同计算机之间的通信,对于网站,通信模型是服务器与客户端之间的通信。两端都建立一个socket对象,然后通过socket对象对数据进行传输。通常服务器处于一个无限循环,等待客户端的连接。

2. 设计步骤

(1)服务器端编程的步骤

①加载套接字库,创建套接字WSAStartup();

在使用socket之前要进行版本的设定和初始化,应用程序只能在一次成功的WSAStartup()调用之后才能调用进一步的Windows Sockets API函数。根据版本初始化windows socket,返回0表示成功。

②创建套接字,使用TCP协议;

有套接字的接口才能进行通信。

③绑定套接字到一个 IP 地址和一个端口上(bind());

用bind()函数确定socket各种属性。

④将套接字设置为监听模式等待连接请求(listen());

⑤循环等待请求到来,每接受一个连接请求,返回一个新的对应于此次连接的套接字(accept());

accept()是一个阻塞函数,如果没有客户端请求,连接会一直等待在这里。该函数会返回一个新的套接字,这个新的套接字是用来与客户端通信的套接字,之前那个套接字是用来监听的套接字。

⑥用返回的套接字和客户端进行通信(send()/recv());

⑦关闭套接字,关闭加载的套接字库(closesocket())。

(2)客户端编程的步骤

①加载套接字库,创建套接字WSAStartup();

要连接的服务器的ip,因为现在服务器端就是本机,所以写本机ip,127.0.0.1一个特殊的IP地址,表示是本机的IP地址。

②向服务器发出连接请求(connect());

如果没有成功连接到服务器,一直循环,直至连接上为止。

③和服务器端进行通信(send()/recv());

④关闭套接字,关闭加载的套接字库(closesocket())。

详细设计 

程序流程图

 实现代码

客户端

myClient.h

#ifndef MYCLIENT_H
#define MYCLIENT_H

#include <QWidget>
#include <iostream>
#include "winsock2.h"
#include "stdlib.h"
#include "stdio.h"
#include "string"
#include <QThread>
#pragma comment (lib, "ws2_32.lib")

QT_BEGIN_NAMESPACE
namespace Ui { class MyClient; }
QT_END_NAMESPACE

class MyClient : public QWidget
{
    Q_OBJECT
public:
    MyClient(QWidget *parent = nullptr);
    ~MyClient();
private slots:
    void on_btcon_clicked();
    void on_btsend_clicked();
    void on_btclose_clicked();
private:
    Ui::MyClient *ui;
};

class WorkThread : public QThread
{
public:
    WorkThread();
protected:
    void run();
};
#endif // MYCLIENT_H

main.cpp

主要是解决中文乱码问题,这里给出解决方案的一种

//添加库
#include <QTextCodec>
//设置编码GBK
QTextCodec::setCodecForLocale(QTextCodec::codecForName("GBK"));

myClient.cpp

#include "myclient.h"
#include "ui_myclient.h"
Ui::MyClient* mui;

SOCKET serverSocket;//服务器

MyClient::MyClient(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::MyClient)
{
    ui->setupUi(this);
	mui = ui;
}

void MyClient::on_btcon_clicked()
{
	WorkThread* thread = new WorkThread();
	thread->start();
}

void MyClient::on_btsend_clicked()
{
	char* buff;
	QString str = mui->tesend->toPlainText();
	QByteArray ba = str.toLocal8Bit();
	buff = ba.data();
	int r = send(serverSocket, buff, strlen(buff), NULL);
    //根据r的值查看是否发送成功
}

void recvAndShow()
{
	int r, i = 0;
	char buff[256];
	while (1)
	{
		memset(buff, 0, 256);
		r = recv(serverSocket, buff, 255, NULL);
		if (r > 0)
		{
			mui->tbrecv->append(QString::fromLocal8Bit(buff,256));
			i++;
		}
	}
}

void MyClient::on_btclose_clicked()
{
	closesocket(serverSocket);
	WSACleanup();
	return;
}

MyClient::~MyClient()
{
	delete ui;
}

WorkThread::WorkThread()
{
}

void WorkThread::run()
{
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData) != 0;//成功==0
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)//请求版本失败
	{
		return ;
	}
	//创建socket
	serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (serverSocket == INVALID_SOCKET)//创建socket失败!
	{
		return ;
	}
	//地址族
	SOCKADDR_IN addr = { 0 };
	//初始化地址
	int port = mui->leport->text().toInt();
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);//端口号~65535尽量大于1W

	int r = ::connect(serverSocket, (SOCKADDR*)&addr, sizeof addr);

	struct sockaddr_in conn;
	memset(&conn, 0, sizeof(struct sockaddr_in));
	int len = sizeof(conn);
	int ret = ::getsockname(serverSocket, (SOCKADDR*)&conn, &len);

	mui->luser->setText(QString(QLatin1String(inet_ntoa(conn.sin_addr)))+QString::number(ntohs(conn.sin_port)));
	if (r == -1)//连接服务器失败!
	{
		return ;
	}
	CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)recvAndShow, NULL, NULL, NULL);
}

Ui设计

 服务端

myServer.h

#ifndef MYSERVER_H
#define MYSERVER_H

#include <QWidget>
#include <iostream>
#include "winsock2.h"
#include "stdlib.h"
#include "stdio.h"
#include "string"
#include <QByteArray>
#include <QThread>
#pragma comment (lib, "ws2_32.lib")

QT_BEGIN_NAMESPACE
namespace Ui { class MyServer; }
QT_END_NAMESPACE

class MyServer : public QWidget
{
    Q_OBJECT
public:
    MyServer(QWidget *parent = nullptr);
    ~MyServer();
private slots:
    void on_btopen_clicked();
    void on_btclose_clicked();
private:
    Ui::MyServer *ui;
};

class WorkThread : public QThread
{
public:
    WorkThread();
protected:
    void run();
};
void communication(LPVOID n);
#endif // MYSERVER_H

main.cpp

与客户端相同

myServer.cpp

#include "myserver.h"
#include "ui_myserver.h"
Ui::MyServer* mui;
SOCKET clientSocket[1024];
SOCKET serverSocket;
SOCKADDR_IN addr = { 0 };
int k = 0;

MyServer::MyServer(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::MyServer)
{
    ui->setupUi(this);
    mui = ui;
}
MyServer::~MyServer()
{
    delete ui;
}
void MyServer::on_btopen_clicked()
{
    WorkThread* thread = new WorkThread();
    thread->start();
}
void MyServer::on_btclose_clicked()
{
    closesocket(serverSocket);
    closesocket(*clientSocket);
    WSACleanup();
}

void communication(LPVOID n)
{
    char buff[256];
    int r;
    int i = (int)n;
    while (1)
    {
        struct sockaddr_in sa;
        int len = sizeof(sa);
        if (!getpeername(clientSocket[i - 1], (struct sockaddr*)&sa, &len))
        {
            //printf("对方IP:%s ", inet_ntoa(sa.sin_addr));
            //printf("对方PORT:%d ", ntohs(sa.sin_port));
        }
        memset(buff, 0, sizeof(buff));
        r = recv(clientSocket[i - 1], buff, 255, NULL);
        if (r > 0)
        {
            char* tem = buff;
            QString s = QString(QLatin1String(inet_ntoa(sa.sin_addr))) + ":" + QString::number(ntohs(sa.sin_port)) + ":" + QString::fromLocal8Bit(buff, 256);
            mui->tbinfor->append(s);
            for (int j = 0; j < k; j++)
            {
                QByteArray ba = s.toLocal8Bit();
                char* buff1 = ba.data();
                int len = send(clientSocket[j], buff1, strlen(buff1), NULL);
                if (len <= 0) {
                    return ;
                }
            }
        }

    }
}

WorkThread::WorkThread()
{
}

void WorkThread::run()
{
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData) != 0;//成功==0
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
    {
        return;
    }
    //创建socket
    //sockSer = socket(AF_INET, SOCK_STREAM,IPPROTO_TCP);//AF=Address family ,ipv4,TCP,0
    serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (serverSocket == INVALID_SOCKET)
    {
        return;
    }
    //初始化地址
    int port = mui->leport->text().toInt();
    addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);//端口号~65535
    int r = bind(serverSocket, (SOCKADDR*)&addr, sizeof(addr));
    if (r == -1)//绑定失败
    {
        return;
    }
    r = listen(serverSocket, 10);
    if (r == -1)//监听失败
    {
        return;
    }
    //连接
    //地址族
    SOCKADDR_IN cAddr = { 0 };
    int len = sizeof(cAddr);

    int i = 0;
    while (i < 1024)
    {
        clientSocket[i++] = accept(serverSocket, (sockaddr*)&cAddr, &len);
        k++;
        if (clientSocket[i - 1] == SOCKET_ERROR)//错误的客户端!
        {
            closesocket(serverSocket);
            WSACleanup();
            return;
        }
        mui->tbuser->append(QString(QLatin1String(inet_ntoa(cAddr.sin_addr))) + ":" + QString::number(ntohs(cAddr.sin_port)) + QString::fromLocal8Bit(" 上线了!"));
        CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)communication, (LPVOID)i, NULL, NULL);
    }
    return;
}

ui设计

实验结果

 

 

  • 6
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
c++/qt写的项目,项目都经测试过,真实可靠,可供自己学习c++/qtQt是一个用标准C++编写的跨平台开发类库,它对标准C++进行了扩展,引入了元对象系统、信号与槽、属性等特性,使应用程序的开发变得更高效。 Qt类库中大量的类以模块形式分类组织的,包括基本模块和扩展模块等。一个模块通常就是一个编程主题,如数据库、图表、网络等。 一、Qt核心特点 1.1.概述 Qt本身并不是一种编程语言,它本质上是一个跨平台的C++开发类库,是用标准C++编写的类库,它为开发GUI应用程序和非GUI应用程序提供了各种类。 Qt对标准C++进行了扩展,引入了一些新概念和功能,例如信号和槽、对象属性等。Qt的元对象编译器(Meta-Object Compiler,MOC)是一个预处理器,在源程序被编译前先将这些Qt特性的程序转换为标准C++兼容的形式,然后再由标准C++编译器进行编译。这就是为什么在使用信号与槽机制的类里,必须添加一个Q_OBJECT宏的原因,只有添加了这个宏,moc才能对类里的信号与槽的代码进行预处理。 Qt Core模块是Qt类库的核心,所有其他模块都依赖于此模块,如果使用qmake来构建项目,Qt Core模块则是被自动加入的。 QtC++语言增加的特性就是在Qt Core模块里实现的,这些扩展特性由Qt的元对象系统实现,包括信号与槽机制、属性系统、动态类型转换等。 1.2.元对象系统 Qt的元对象系统(Meta-Object-System)提供了对象之间通信的信号与槽机制、运行时类型信息和动态属性系统。 元对象系统由以下三个基础组成: 1.QObject类是所有使用元对象系统的类的基类; 2.在一个类的private部分声明Q_OBJECT宏,使得类可以使用元对象的特性,如动态属性、信号与槽。 3.MOC(元对象编译器)为每个QObject的子类提供必要的代码来实现元对象系统的特征。 构建项目时,MOC工具读取C++源文件,当它发现类的定义里有Q_OBJECT宏时,它就会为这个类生成另外一个包含有元对象支持代码的C++源文件,这个生成的源文件连同类的实现文件一起被编译和连接。 除了信号和槽机制外,元对象还提供如下一些功能。 1.QObject::metaObject()函数返回类关联的元对象,元对象类QMetaObject包含了访问元对象的一些接口函数,例如QMetaObject::className()函数可在运行时返回类的名称字符串。 QObject obj=new QPushButton; obj->metaObject()->className(); 2.QMetaObject::newInstance()函数创建类的一个新的实例。 3.QObject::inherits(const charclassName)函数判断一个对象实例是否是名称为className的类或QObject的子类的实例。 1.3.属性系统 1.属性定义 Qt提供一个Q_PROPERTY()宏可以定义属性,它也是属于元对象系统实现的。Qt的属性系统与C++编译器无关,可以用任何标准的C++编译器编译定义了属性的Qt C++程序。 2.属性的使用 不管是否用READ和WRITE定义了接口函数,只要知道属性名称,就可以通过QObject::property()读取属性值,并通过QObject::setProperty()设置属性值。 3.动态属性 QObject::setProperty()函数可以在运行时为类定义一个新的属性,称之为动态属性。动态属性是针对类的实例定义的。 动态属性可以使用QObject::property()查询,就如在类定义里用Q_PROPERTY宏定义的属性一样。 例如,在数据表编辑界面上,一些字段是必填字段,就可以在初始化界面时为这些字段的关联显示组件定义一个新的required属性,并设置值为“true"。 4.类的附加信息 属性系统还有一个宏Q_CLASSINFO(),可以为类的元对象定义”名称——值“信息。
c++/qt写的项目,项目都经测试过,真实可靠,可供自己学习c++/qtQt是一个用标准C++编写的跨平台开发类库,它对标准C++进行了扩展,引入了元对象系统、信号与槽、属性等特性,使应用程序的开发变得更高效。 Qt类库中大量的类以模块形式分类组织的,包括基本模块和扩展模块等。一个模块通常就是一个编程主题,如数据库、图表、网络等。 一、Qt核心特点 1.1.概述 Qt本身并不是一种编程语言,它本质上是一个跨平台的C++开发类库,是用标准C++编写的类库,它为开发GUI应用程序和非GUI应用程序提供了各种类。 Qt对标准C++进行了扩展,引入了一些新概念和功能,例如信号和槽、对象属性等。Qt的元对象编译器(Meta-Object Compiler,MOC)是一个预处理器,在源程序被编译前先将这些Qt特性的程序转换为标准C++兼容的形式,然后再由标准C++编译器进行编译。这就是为什么在使用信号与槽机制的类里,必须添加一个Q_OBJECT宏的原因,只有添加了这个宏,moc才能对类里的信号与槽的代码进行预处理。 Qt Core模块是Qt类库的核心,所有其他模块都依赖于此模块,如果使用qmake来构建项目,Qt Core模块则是被自动加入的。 QtC++语言增加的特性就是在Qt Core模块里实现的,这些扩展特性由Qt的元对象系统实现,包括信号与槽机制、属性系统、动态类型转换等。 1.2.元对象系统 Qt的元对象系统(Meta-Object-System)提供了对象之间通信的信号与槽机制、运行时类型信息和动态属性系统。 元对象系统由以下三个基础组成: 1.QObject类是所有使用元对象系统的类的基类; 2.在一个类的private部分声明Q_OBJECT宏,使得类可以使用元对象的特性,如动态属性、信号与槽。 3.MOC(元对象编译器)为每个QObject的子类提供必要的代码来实现元对象系统的特征。 构建项目时,MOC工具读取C++源文件,当它发现类的定义里有Q_OBJECT宏时,它就会为这个类生成另外一个包含有元对象支持代码的C++源文件,这个生成的源文件连同类的实现文件一起被编译和连接。 除了信号和槽机制外,元对象还提供如下一些功能。 1.QObject::metaObject()函数返回类关联的元对象,元对象类QMetaObject包含了访问元对象的一些接口函数,例如QMetaObject::className()函数可在运行时返回类的名称字符串。 QObject obj=new QPushButton; obj->metaObject()->className(); 2.QMetaObject::newInstance()函数创建类的一个新的实例。 3.QObject::inherits(const charclassName)函数判断一个对象实例是否是名称为className的类或QObject的子类的实例。 1.3.属性系统 1.属性定义 Qt提供一个Q_PROPERTY()宏可以定义属性,它也是属于元对象系统实现的。Qt的属性系统与C++编译器无关,可以用任何标准的C++编译器编译定义了属性的Qt C++程序。 2.属性的使用 不管是否用READ和WRITE定义了接口函数,只要知道属性名称,就可以通过QObject::property()读取属性值,并通过QObject::setProperty()设置属性值。 3.动态属性 QObject::setProperty()函数可以在运行时为类定义一个新的属性,称之为动态属性。动态属性是针对类的实例定义的。 动态属性可以使用QObject::property()查询,就如在类定义里用Q_PROPERTY宏定义的属性一样。 例如,在数据表编辑界面上,一些字段是必填字段,就可以在初始化界面时为这些字段的关联显示组件定义一个新的required属性,并设置值为“true"。 4.类的附加信息 属性系统还有一个宏Q_CLASSINFO(),可以为类的元对象定义”名称——值“信息。
c++/qt写的项目,项目都经测试过,真实可靠,可供自己学习c++/qtQt是一个用标准C++编写的跨平台开发类库,它对标准C++进行了扩展,引入了元对象系统、信号与槽、属性等特性,使应用程序的开发变得更高效。 Qt类库中大量的类以模块形式分类组织的,包括基本模块和扩展模块等。一个模块通常就是一个编程主题,如数据库、图表、网络等。 一、Qt核心特点 1.1.概述 Qt本身并不是一种编程语言,它本质上是一个跨平台的C++开发类库,是用标准C++编写的类库,它为开发GUI应用程序和非GUI应用程序提供了各种类。 Qt对标准C++进行了扩展,引入了一些新概念和功能,例如信号和槽、对象属性等。Qt的元对象编译器(Meta-Object Compiler,MOC)是一个预处理器,在源程序被编译前先将这些Qt特性的程序转换为标准C++兼容的形式,然后再由标准C++编译器进行编译。这就是为什么在使用信号与槽机制的类里,必须添加一个Q_OBJECT宏的原因,只有添加了这个宏,moc才能对类里的信号与槽的代码进行预处理。 Qt Core模块是Qt类库的核心,所有其他模块都依赖于此模块,如果使用qmake来构建项目,Qt Core模块则是被自动加入的。 QtC++语言增加的特性就是在Qt Core模块里实现的,这些扩展特性由Qt的元对象系统实现,包括信号与槽机制、属性系统、动态类型转换等。 1.2.元对象系统 Qt的元对象系统(Meta-Object-System)提供了对象之间通信的信号与槽机制、运行时类型信息和动态属性系统。 元对象系统由以下三个基础组成: 1.QObject类是所有使用元对象系统的类的基类; 2.在一个类的private部分声明Q_OBJECT宏,使得类可以使用元对象的特性,如动态属性、信号与槽。 3.MOC(元对象编译器)为每个QObject的子类提供必要的代码来实现元对象系统的特征。 构建项目时,MOC工具读取C++源文件,当它发现类的定义里有Q_OBJECT宏时,它就会为这个类生成另外一个包含有元对象支持代码的C++源文件,这个生成的源文件连同类的实现文件一起被编译和连接。 除了信号和槽机制外,元对象还提供如下一些功能。 1.QObject::metaObject()函数返回类关联的元对象,元对象类QMetaObject包含了访问元对象的一些接口函数,例如QMetaObject::className()函数可在运行时返回类的名称字符串。 QObject obj=new QPushButton; obj->metaObject()->className(); 2.QMetaObject::newInstance()函数创建类的一个新的实例。 3.QObject::inherits(const charclassName)函数判断一个对象实例是否是名称为className的类或QObject的子类的实例。 1.3.属性系统 1.属性定义 Qt提供一个Q_PROPERTY()宏可以定义属性,它也是属于元对象系统实现的。Qt的属性系统与C++编译器无关,可以用任何标准的C++编译器编译定义了属性的Qt C++程序。 2.属性的使用 不管是否用READ和WRITE定义了接口函数,只要知道属性名称,就可以通过QObject::property()读取属性值,并通过QObject::setProperty()设置属性值。 3.动态属性 QObject::setProperty()函数可以在运行时为类定义一个新的属性,称之为动态属性。动态属性是针对类的实例定义的。 动态属性可以使用QObject::property()查询,就如在类定义里用Q_PROPERTY宏定义的属性一样。 例如,在数据表编辑界面上,一些字段是必填字段,就可以在初始化界面时为这些字段的关联显示组件定义一个新的required属性,并设置值为“true"。 4.类的附加信息 属性系统还有一个宏Q_CLASSINFO(),可以为类的元对象定义”名称——值“信息。
很高兴能够回答你的问题。以下是一个简单的即时通信系统的C++网络编程示例,基于Linux平台使用socket库进行开发。 客户端代码: ```c++ #include <iostream> #include <cstring> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> using namespace std; int main() { // 创建socket int client_socket = socket(AF_INET, SOCK_STREAM, 0); // 配置服务器地址 sockaddr_in server_address{}; server_address.sin_family = AF_INET; server_address.sin_port = htons(8080); server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); // 连接服务器 int connect_result = connect(client_socket, (sockaddr *) &server_address, sizeof(server_address)); if (connect_result == -1) { cout << "连接服务器失败!" << endl; return -1; } // 发送消息 char message[1024] = {0}; while (true) { cin.getline(message, sizeof(message)); if (strlen(message) == 0) { continue; } send(client_socket, message, strlen(message), 0); memset(message, 0, sizeof(message)); } // 关闭socket close(client_socket); return 0; } ``` 服务器端代码: ```c++ #include <iostream> #include <cstring> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> using namespace std; int main() { // 创建socket int server_socket = socket(AF_INET, SOCK_STREAM, 0); // 配置服务器地址 sockaddr_in server_address{}; server_address.sin_family = AF_INET; server_address.sin_port = htons(8080); server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); // 绑定socket和地址 bind(server_socket, (sockaddr *) &server_address, sizeof(server_address)); // 监听客户端连接 listen(server_socket, 10); // 接受客户端连接 sockaddr_in client_address{}; socklen_t client_address_len = sizeof(client_address); int client_socket = accept(server_socket, (sockaddr *) &client_address, &client_address_len); // 接收消息 char message[1024] = {0}; while (true) { int recv_result = recv(client_socket, message, sizeof(message), 0); if (recv_result <= 0) { break; } cout << "收到消息:" << message << endl; memset(message, 0, sizeof(message)); } // 关闭socket close(client_socket); close(server_socket); return 0; } ``` 在上面的代码中,客户端和服务器端都使用了socket库中的函数来完成网络通信。客户端使用`socket`函数创建socket,使用`connect`函数连接服务器,并使用`send`函数发送消息。服务器端使用`socket`函数创建socket,使用`bind`函数将socket与服务器地址绑定,使用`listen`函数监听客户端连接,并使用`accept`函数接受客户端连接,最后使用`recv`函数接收客户端发送的消息。 当然,这只是一个简单的示例。在实际开发中,你需要考虑更多的细节,例如如何处理多个客户端连接、如何处理异常情况等等。 希望这个示例能够帮助你了解Linux下C++ socket网络编程的基本流程和方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值