QT--网络篇

  1. 如果QT头文件找不到QTcpSocket、QTcpSocket、QTcpServer、QtNetwork ,那么可能是pro文件中缺少QT += network这行代码
    在这里插入图片描述

客户端QTcpSocket

  1. void QTcpSocket::connectToHost( QString servip, quint16 port );
  • connectToHost 函数会尝试与指定的服务器建立 TCP 连接。如果连接成功,将会触发 connected 信号,如果连接失败,将会触发 errorOccurred 信号。属于QTcpSocket类
  • QString servip:这是服务器的 IP 地址或主机名。QString 是 Qt 提供的一个字符串类,用于处理文本字符串。
  • quint16 port:这是服务器的端口号。quint16 是 Qt 提供的一个无符号 16 位整数类型,通常用于表示端口号。
    代码举例
QTcpSocket *socket = new QTcpSocket;

// 连接到服务器,假设服务器的 IP 地址是 "192.168.1.100" 端口号是 12345
socket->connectToHost("192.168.1.100", 12345);

// 连接成功时,触发的槽函数
connect(socket, SIGNAL(connected()), this, SLOT(onConnected()));

// 连接失败时,触发的槽函数
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));

  • toUShort() 是 Qt 中 QString 类的成员函数,用于将字符串转换为 unsigned short(即 quint16)类型的整数。其作用是将一个表示数字的字符串转换为对应的无符号 16 位整数。这适合于从lineEdit中获取端口号。
  1. QTcpSocket的信号有
    - connected(),当套接字成功连接到目标主机时发出的信号。
    - disconnected(),当套接字与目标主机断开连接时发出的信号。
    - readyRead(),当套接字有新的数据可供读取时发出的信号。
// 假设 ptCliSocket 是一个 QTcpSocket 对象,在构造函数或其他地方已经创建和初始化好了。

// 连接 readyRead 信号到 ptCliSocketReadyReadSlotFun 槽函数,即当 ptCliSocket 有可读数据时调用 ptCliSocketReadyReadSlotFun 函数。
connect(ptCliSocket, SIGNAL(readyRead()), this, SLOT(ptCliSocketReadyReadSlotFun()));

void Widget::ptCliSocketReadyReadSlotFun()
{
    char buf[512] = {0}; // 创建一个 512 字节大小的字符数组,用于存储接收到的数据。

    int ret = ptCliSocket->read(buf, sizeof(buf)); // 从 ptCliSocket 中读取数据到 buf 中,最多读取 sizeof(buf) 字节的数据。
    if (ret < 0) { // 如果读取失败(返回值小于 0),显示接收错误信息到界面上的 labelTips,并返回。
        ui->labelTips->setText("recv err");
        return;
    }

    // 将读取到的数据显示在界面上的 textEdit 控件中。
    ui->textEdit->setText(buf);
}

-------------------------------------------------------------------------
//上面是read读出,以下是write写入
在 QTcpSocket 类中,调用 write 方法写入数据并没有对应的信号。写入数据是一个操作,它不会触发信号。信号通常用于通知接收到新数据(readyRead 信号)或连接状态变化等事件,而不是在发送数据时触发的。

当你调用 QTcpSocket 的 write 方法写入数据时,它是一个阻塞操作,意味着程序会等待数据完全写入或发生错误才会继续执行后续代码。如果需要了解写入操作的结果,你可以根据返回值来判断是否写入成功,而不需要依赖信号来通知。

QTcpSocket *socket = new QTcpSocket;

// 假设已连接到服务器

char data[] = "Hello, server!"; // 准备要发送的数据,这里使用 C 风格的字符串
quint64 size = strlen(data);   // 获取要发送数据的长度

qint64 bytesWritten = socket->write(data, size); // 向服务器发送数据,并记录实际写入的字节数
if (bytesWritten == -1) { // 如果写入操作返回 -1,表示出现错误
    qDebug() << "Error writing to socket:" << socket->errorString(); // 输出错误信息
} else { // 写入成功的情况
    qDebug() << "Successfully wrote" << bytesWritten << "bytes to socket."; // 输出成功写入的字节数
}




  1. 关于write和read
  • quint64 QIODevice::write(char *data, quint64 size); 向服务器发送消息: 返回值-和普通的write保持一致
  • quint64 QIODevice::read (char *data, quint64 size); 返回值和以前一致,0表示没有数据,注意,gui进程如果使用read,一定要确保有数据才去读;所以一般绑定readyread()信号
  1. close
  • 关闭连接:这是 QIODevice 类的成员函数,用于关闭设备(例如文件、套接字等)。在网络编程中,特别是在 QTcpSocket 类中,调用 close() 方法会关闭当前的网络连接。
  • :Qt 中的大多数函数都是非阻塞的。这意味着调用 close() 方法不会立即阻塞程序的执行,而是将关闭操作放入事件循环中处理,使得程序可以继续执行后续代码或响应其他事件。具体来说,在网络编程中,当调用 close() 方法时,它会发送关闭请求,并立即返回,而实际的关闭操作会在稍后异步完成。
  1. 一些小函数交互类
  • toStdString()
    • toStdString() 是 Qt 中的一个成员函数,用于将 Qt 的字符串类型转换为标准的 C++ 字符串 std::string。在实际开发中,这个函数经常用于将 Qt 的字符串类型(如 QString)转换为标准库 std::string,以便在 Qt 和标准 C++ 代码之间进行数据交换或处理。
  • c_str()
    • c_str() 是 C++ 标准库中用于获取字符串的 C 风格(以 null 结尾的字符数组)表示的成员函数。在 Qt 编程中,如果你有一个 QString 或 QByteArray,你可以使用 c_str() 方法来获取其对应的 C 风格字符串指针,即 const char * 类型的指针。
代码举例
#include <QString>
#include <QByteArray>
#include <iostream>

int main() {
    QString qtString = "Hello, Qt!";
    QByteArray byteArray = "Hello, QByteArray!";
    
    // 使用 c_str() 获取 C 风格字符串,并输出
    const char *cString1 = qtString.toStdString().c_str();
    const char *cString2 = byteArray.toStdString().c_str();
    
    // 输出 C 风格字符串
    std::cout << "C string (from QString): " << cString1 << std::endl;
    std::cout << "C string (from QByteArray): " << cString2 << std::endl;
    
    // 使用 toStdString() 直接转换为 std::string,然后输出
    std::string stdString1 = qtString.toStdString();
    std::string stdString2 = byteArray.toStdString();
    
    std::cout << "std::string (from QString): " << stdString1 << std::endl;
    std::cout << "std::string (from QByteArray): " << stdString2 << std::endl;
    
    return 0;
}
-------------------------------------------------------------------------------
例子2 

void Widget::btnSendClickedSlotFun()
{
    QString str = ui->leSendContext->text();
    std::string sstr = str.toStdString();
    const char *pch = sstr.c_str();

    int ret = ptCliSocket->write(pch,sstr.length());
    if(ret <0){
        ui->labelTips->setText("send err!!!");
        return;
    }
    return;
}

服务器QTcpServer

  • QTcpServer 是 Qt 网络模块中的一个类,用于创建一个 TCP 服务器,可以监听指定的地址和端口,接受客户端的连接请求。
1.QTcpServer 的信号
  • newConnection(),当有新的连接到达时,会触发这个信号。可以连接到一个槽函数,用于处理新的 QTcpSocket 连接。
connect(server,SIGNAL(newConnection()),this,SLOT(serverNewConnectionSlot()  ));
  • acceptError(QAbstractSocket::SocketError),这个信号在服务器在接受连接时发生错误时发出。可以通过连接到一个槽函数,来处理这些错误。(一般不用)。
connect(server, SIGNAL(acceptError(QAbstractSocket::SocketError)), 
                this, SLOT(onAcceptError(QAbstractSocket::SocketError)));
2. nextPendingConnection 下一个挂起连接
  • nextPendingConnection 是 QTcpServer 类的一个成员函数,其作用是返回下一个挂起的连接请求,并生成一个与该连接相关的 QTcpSocket 对象。这个函数通常在处理 newConnection 信号的槽函数中调用,用于获取新的客户端连接。
// 接受新的连接
        QTcpSocket *socket = server->nextPendingConnection();
  • 如果没有挂起的连接请求,则返回 nullptr。
  • QTcpSocket::readyRead 信号: 当有数据可读时触发,用于读取客户端发送的数据。
  • QTcpSocket::disconnected 信号: 当连接断开时触发,用于清理资源。
  • tips:由于QTcpServer 默认支持多个并发连接。可以在同一时间与多个客户端进行通信。你可以通过处理 newConnection 信号来接受多个客户端连接,并使用 QTcpSocket 对象与每个客户端进行独立的通信。
  • 在widget.h中定义**QList<QTcpSocket*> sockets;*是在 Qt 中声明一个名为sockets的列表,该列表包含 QTcpSocket 类型的指针。这意味着sockets是一个可以存储多个 QTcpSocket 指针的容器,方便管理和操作多个客户端连接。比如使用sockets.append(newSocket);//将新的Socket指针存入数组中
3.判断是哪个客户端发来了信息
  • 对socket进行read操作即可,判断读取是否非0,非0即是该socket发来的信息
void Widget::pCliSockReadyReadSlot()
{
    char buf[512] = {0}; // 用于存储从客户端读取的数据的缓冲区,初始清空

    // 遍历所有已连接的客户端 socket
    for(int i = 0; i < sockets.length(); i++){
        QTcpSocket *p = sockets.at(i); // 获取第 i 个客户端 socket

        int ret = p->read(buf, sizeof(buf)); // 从客户端 socket 中读取数据到 buf 中,返回值 ret 表示实际读取的字节数。


        if(ret == 0){
            continue; // 如果未读取到数据,继续下一个客户端 socket 的处理
        }

        // 显示接收到的消息在界面的 textEdit 中
        ui->textEdit->setText(buf);

        char sendbuf[] = "hello client, Ur msg be got!";
        p->write(sendbuf, strlen(sendbuf)); // 向客户端发送响应消息
    }
}

4.判断是哪个客户端关闭了连接
  • state()
  • QTcpServer 类具有一个 state() 函数,用于获取当前 TCP 服务器的状态。这个函数返回一个枚举值 QAbstractSocket::SocketState,表示服务器当前的连接状态。这个状态对于管理和监控服务器的运行状态非常有用。
    有以下几种状态值
状态值含义
QAbstractSocket::UnconnectedState服务器处于未连接状态,即尚未开始监听任何客户端连接。
QAbstractSocket::HostLookupState服务器正在进行主机名查找,用于解析 IP 地址。
QAbstractSocket::ConnectingState服务器正在尝试与另一个设备建立连接。
QAbstractSocket::ConnectedState服务器已成功连接到另一个设备。
QAbstractSocket::BoundState服务器已绑定到本地地址和端口,等待连接请求。
QAbstractSocket::ClosingState服务器正在关闭连接,即将结束当前的连接。
QAbstractSocket::ListeningState服务器正在监听传入的连接请求。

一般用第一个判断是否断开连接

void Widget::pCliSockDisconnectionSlot()
{
    for(int i = 0; i < sockets.length(); i++){
        QTcpSocket *p = sockets.at(i); // 获取第 i 个客户端的 QTcpSocket 对象指针

        // 检查客户端的连接状态是否为未连接状态
        if(p->state() == QAbstractSocket::UnconnectedState){
            p->close(); // 如果客户端已经断开连接,关闭当前的 QTcpSocket 对象
            sockets.removeAt(i); // 从列表中移除已经断开连接的客户端 QTcpSocket 对象
            ui->textEdit->setText("found cli shutdown!"); // 在界面的 textEdit 中显示找到客户端断开连接的消息
        }
    }
}

5.如何启动服务器
  • listen(),listen() 函数是用于 QTcpServer 类的成员函数,用来开始监听指定地址和端口的连接请求。它的作用是启动服务器,使其能够接受来自客户端的连接。
bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)
参数
address:要监听的本地地址。默认为 QHostAddress::Any,表示监听所有可用的网络接口。
port:要监听的端口号。如果为 0,系统将分配一个随机的未使用端口。
返回值
true:如果成功开始监听指定的地址和端口。
false:如果不能开始监听,可能是由于地址或端口被占用或其他原因导致的失败。
  • 代码举例
void Widget::btnSetupClickedSlot()
{
    // 从界面的 lePort LineEdit 中获取端口号,并转换为 quint16 类型
    quint16 port = ui->lePort->text().toUShort();

    // 调用 ptTcpServ(QTcpServer 对象)的 listen 函数,开始监听任意本地地址和指定端口
    bool r = ptTcpServ->listen(QHostAddress::Any, port);

    // 检查 listen 函数返回值,r 为 false 表示服务器启动失败
    if(r == false){
        ui->textEdit->setText("serv setup failed!"); // 在界面的 textEdit 中显示服务器设置失败的消息
        return; // 退出函数
    }

    ui->textEdit->setText("serv setup success"); // 在界面的 textEdit 中显示服务器设置成功的消息
    // 服务器启动成功,等待客户端连接的信号处理
}

代码举例

服务器代码
//widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include<QTcpServer>
#include<QTcpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
public slots:
    void serverNewConnectedSlotFun();
    void socketReadyReadSlotFun();
    void socketDisconnectedSlotFun();
    void btnLinkClickedSlotFun();
    void socketBoxCurrentIndexChanged(int);



private:
    Ui::Widget *ui;
    QTcpServer *server;
    QList<QTcpSocket*> sockets;


};
#endif // WIDGET_H



//widget.cpp
#include "widget.h"
#include "ui_widget.h"

// Widget类的构造函数,初始化UI和QTcpServer对象,并设置连接信号槽
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this); // 设置UI
    server = new QTcpServer; // 创建QTcpServer对象
    connect(server, SIGNAL(newConnection()), this, SLOT(serverNewConnectedSlotFun())); // 连接新连接信号到槽函数
    ui->btnLink->setText("打开服务器"); // 设置按钮文本
    ui->linePort->setText("8888"); // 设置端口文本
    connect(ui->btnLink, SIGNAL(clicked()), this, SLOT(btnLinkClickedSlotFun())); // 连接按钮点击信号到槽函数
    connect(ui->socketsBox, SIGNAL(currentIndexChanged(int)), this, SLOT(socketBoxCurrentIndexChanged(int))); // 连接下拉框改变信号到槽函数
}

// Widget类的析构函数,释放UI资源
Widget::~Widget()
{
    delete ui; // 释放UI资源
}

// 当有新连接时,处理新连接的槽函数
void Widget::serverNewConnectedSlotFun()
{
    QTcpSocket *socket = server->nextPendingConnection(); // 获取新连接的socket
    connect(socket, SIGNAL(readyRead()), this, SLOT(socketReadyReadSlotFun())); // 连接数据可读信号到槽函数
    connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnectedSlotFun())); // 连接断开连接信号到槽函数

    sockets.append(socket); // 将新连接的socket添加到列表中
    QString str = QString("QTcpSocket(%1)").arg(reinterpret_cast<quintptr>(socket), 0, 16); // 获取socket的地址字符串
    ui->socketsBox->addItem(str); // 将地址字符串添加到下拉框
    ui->textEditRecv->setText("new connect"); // 设置文本编辑框的文本
}

// 当有数据可读时,处理读取数据的槽函数
void Widget::socketReadyReadSlotFun()
{
    char buf[512] = {0}; // 创建缓冲区
    QTcpSocket *p;
    for(int i = 0; i < sockets.length(); i++) { // 遍历所有socket
        p = sockets.at(i); // 获取当前socket
        int ret = p->read(buf, sizeof(buf)); // 读取数据到缓冲区
        if(ret == 0) {
            continue; // 如果没有数据读取,继续下一次循环
        }
        ui->textEditRecv->setText(buf); // 将读取的数据设置到文本编辑框
        char sendbuf[] = "hello client"; // 创建发送数据
        p->write(sendbuf, strlen(sendbuf)); // 发送数据到客户端
    }
}

// 当连接断开时,处理断开连接的槽函数
void Widget::socketDisconnectedSlotFun()
{
    for(int i = 0; i < sockets.length(); i++) { // 遍历所有socket
        QTcpSocket *p = sockets.at(i); // 获取当前socket
        if(p->state() == QAbstractSocket::UnconnectedState) { // 检查socket是否断开连接
            p->close(); // 关闭socket
            sockets.removeAt(i); // 从列表中移除socket
            ui->socketsBox->removeItem(i); // 从下拉框中移除项
            ui->textEditRecv->setText("found cli shutdown"); // 设置文本编辑框的文本
        }
    }
}

// 处理按钮点击事件的槽函数
void Widget::btnLinkClickedSlotFun()
{
    quint16 port = ui->linePort->text().toUShort(); // 获取端口号
    bool ret = server->listen(QHostAddress::Any, port); // 启动服务器监听
    if(ret == false) {
        ui->textEditRecv->setText("打开服务器失败"); // 如果监听失败,设置文本编辑框的文本
        return;
    }
    ui->textEditRecv->setText("打开服务器成功"); // 如果监听成功,设置文本编辑框的文本
}

// 处理下拉框当前索引改变事件的槽函数
void Widget::socketBoxCurrentIndexChanged(int i)
{
    QString send = ui->textEditSend->toPlainText(); // 获取发送数据
    const char *send1 = send.toStdString().c_str(); // 将QString转换为C字符串

    QString str = ui->socketsBox->itemText(i); // 获取下拉框当前选中的文本
    for(int i = 0; i < sockets.length(); i++) { // 遍历所有socket
        QTcpSocket* aimSocket = sockets.at(i); // 获取当前socket
        QString straim = QString("QTcpSocket(%1)").arg(reinterpret_cast<quintptr>(aimSocket), 0, 16); // 获取socket的地址字符串
        if(str == straim) { // 如果地址字符串匹配
            int ret = aimSocket->write(send1, strlen(send1)); // 发送数据到客户端
            if(ret < 0) {
                ui->label->setText("发送失败"); // 如果发送失败,设置标签的文本
            } else {
                ui->label->setText("发送成功"); // 如果发送成功,设置标签的文本
            }
        }
    }
}

客户端代码
//widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include<QTcpSocket>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
public slots:
    void btnlinkClickedSlotFun();
    void socketClientConnectedSlotFun();
    void socketClientReadyReadSlotFun();
    void socketClientDisconnectedSlotFun();
    void btnSendClickedSlotFun();
private:
    Ui::Widget *ui;
    QTcpSocket *SocketClient;
};
#endif // WIDGET_H


//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>

// Widget类的构造函数,初始化UI和QTcpSocket对象,并设置连接信号槽
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this); // 设置UI
    SocketClient = new QTcpSocket; // 创建QTcpSocket对象
    ui->btnlink->setText("connect"); // 设置连接按钮的文本
    connect(ui->btnlink, SIGNAL(clicked()), this, SLOT(btnlinkClickedSlotFun())); // 连接按钮点击信号到槽函数

    connect(SocketClient, SIGNAL(connected()), this, SLOT(socketClientConnectedSlotFun())); // 连接socket连接成功信号到槽函数
    connect(SocketClient, SIGNAL(readyRead()), this, SLOT(socketClientReadyReadSlotFun())); // 连接socket有数据可读信号到槽函数
    connect(SocketClient, SIGNAL(disconnected()), this, SLOT(socketClientDisconnectedSlotFun())); // 连接socket断开连接信号到槽函数

    ui->btnSend->setText("发送"); // 设置发送按钮的文本
    connect(ui->btnSend, SIGNAL(clicked()), this, SLOT(btnSendClickedSlotFun())); // 连接发送按钮点击信号到槽函数
    ui->lineIp->setText("192.168.124.137"); // 设置默认IP地址
    ui->linePort->setText("8888"); // 设置默认端口号
}

// Widget类的析构函数,释放UI资源
Widget::~Widget()
{
    delete ui; // 释放UI资源
}

// 处理连接按钮点击事件的槽函数
void Widget::btnlinkClickedSlotFun()
{
    if(ui->btnlink->text() == "connect") { // 如果按钮文本为"connect"
        QString Ip = ui->lineIp->text(); // 获取IP地址
        quint16 Port = ui->linePort->text().toUShort(); // 获取端口号
        SocketClient->connectToHost(Ip, Port); // 连接到服务器
    }
    else if(ui->btnlink->text() == "disconnect") // 如果按钮文本为"disconnect"
        SocketClient->close(); // 关闭连接
    else
        ui->labelTips->setText("connect error"); // 如果文本为其他值,显示错误信息
}

// 处理socket连接成功事件的槽函数
void Widget::socketClientConnectedSlotFun()
{
    ui->btnlink->setText("disconnect"); // 设置按钮文本为"disconnect"
    ui->labelTips->setText("Tips:connected success"); // 显示连接成功信息
}

// 处理socket有数据可读事件的槽函数
void Widget::socketClientReadyReadSlotFun()
{
    char buf[512] = {0}; // 创建缓冲区
    int ret = SocketClient->read(buf, sizeof(buf)); // 读取数据到缓冲区
    if(ret < 0) {
        ui->labelTips->setText("Tips:读取错误"); // 如果读取失败,显示错误信息
        return;
    }
    ui->textRecv->setText(buf); // 将读取的数据设置到文本框
}

// 处理socket断开连接事件的槽函数
void Widget::socketClientDisconnectedSlotFun()
{
    ui->btnlink->setText("connect"); // 设置按钮文本为"connect"
    ui->labelTips->setText("Tips:连接断开"); // 显示连接断开信息
}

// 处理发送按钮点击事件的槽函数
void Widget::btnSendClickedSlotFun()
{
    QString send = ui->lineSend->text(); // 获取发送数据
    const char *send1 = send.toStdString().c_str(); // 将QString转换为C字符串

    int ret = SocketClient->write(send1, strlen(send1)); // 发送数据到服务器
    if(ret < 0) {
        ui->labelTips->setText("Tips:发送失败"); // 如果发送失败,显示错误信息
        return;
    }
    return;
}


在这里插入图片描述

  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt是一款跨平台的C++应用程序开发框架,由于其突出的易用性和灵活性,被广泛应用于图形用户界面(GUI)开发领域。 Qt编程先导是由中国著名程序员黄强编写的一本重要教材,专门介绍Qt的基础知识和编程技巧。这本书主要分为三个部分:第一部分介绍了Qt开发的基本概念和重要组件;第二部分介绍了Qt的窗口和布局管理,以及事件和信号槽的使用;第三部分介绍了Qt的高级特性和进阶技巧。 在第一部分中,黄强首先介绍了Qt的安装和配置,以及Qt开发工具Qt Creator的使用方法。之后,他详细介绍了Qt的核心模块和类,如QWidget、QMainWindow、QPushButton等,同时也解释了Qt的一些基本概念,如信号槽机制、事件驱动等。 第二部分主要介绍了Qt的窗口和布局管理,以及事件和信号槽的使用。这些内容是Qt编程中至关重要的基础知识,通过学习这些知识,读者可以掌握如何创建窗口和控件,如何使用布局管理器实现灵活的用户界面,以及如何处理各种事件和信号。 第三部分则涵盖了Qt的高级特性和进阶技巧,包括多线程编程、数据库访问、网络编程等。这些内容更加深入和复杂,读者需要在掌握基础知识之后才能更好地理解和应用。 总的来说,黄强的《Qt编程先导》是一本权威且实用的Qt编程教材,对于想要学习和掌握Qt编程的开发者来说,是一本不可或缺的指南。通过学习这本书,读者可以建立起对Qt开发的全面和系统的理解,为后续的Qt项目开发打下坚实的基础。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值