文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Qt串口回路信号读取多线程14串口测试方案

2024-11-30 05:25

关注

二、环境

三、添加QSerialPort

四、语句介绍

1.数据控制流

数据控制流 USART_HardwareFlowControl ,控制流,这里的流指数据流,在数据传输中,流控制室管理两个节点之间数据传输速率过程,以防止出现接收端的数据缓存区已满,而发送端依然继续发送数据,所以导致数据丢失。

(1) 工作原理

当接受端的数据缓冲区已满,无法处理数据来时,就发出不再接受的信号,发送端则停止发送,直到发送端收到可以继续发送的信号再发送数据,常见的硬件控制流和软件控制流

(2) 硬件控制流

硬件控制流

RTS和CTS原本是用来询问和回答是否可以传输数据。在上面的连接上,就是告诉对方自己子否可以进行通讯,此时RTS和DTR都可以用来对数据流进行控制。

A端DTR(数据设备就绪)发出信号,当B端准备好后,B端的DTR(数据设备就绪)向A端的DSR(通讯设备就绪发送信号),然后就可以通过RTS(请求发送)和DTR(允许发送)来控制通信。

硬件控制流并不是单纯依赖硬件,它仍然需要软件去处理识别,硬件控制流所做的只是给出信号电平。

(3) 软件控制流

软件流控制(Software flow control)是在计算机数据链路中的一种控制方法,特别适合于RS-232串口通信;它是采用特殊字符来船速带内信令,特殊编码字符称作XOFF与XON(分别表示transmit off与transmit on)。因此也被称作XON/XOFF流控制

使用ASCII字符集,XOFF一般为字节值19(十进制),XON为字节值17

(4) 工作方式

注意:是接收方把XON/XOFF信号发给发送方来控制发送方何时发送数据,这些信号是与发送数据的传输方向相反的。

接收方利用XON信号告诉发送方,我已经准备好接受更多的数据了,利用XOFF信号告诉发送方停止发送数据,直到接收方发送XON信号告诉发送方我再次准备好了。

(5) 设置控制流模式

setFlowControl 函数:

// setFlowControl对端口控制进行相关设置 无流量控制
serial->setFlowControl(QSerialPort::NoFlowControl);

设置控制流

2.设置端口

设置端口的名称,此名字可以使用短名称或者长系统地址。

void QSerialPort::setPortName(const QString &name)

3.波特率

输出传输的速率,每秒传输的比特数。

enum QSerialPort::BaudRate
    
/// 可以选择的内容为

Constant

Value

Description

QSerialPort::Baud1200

1200

1200 baud.

QSerialPort::Baud2400

2400

2400 baud.

QSerialPort::Baud4800

4800

4800 baud.

QSerialPort::Baud9600

9600

9600 baud.

QSerialPort::Baud19200

19200

19200 baud.

QSerialPort::Baud38400

38400

38400 baud.

QSerialPort::Baud57600

57600

57600 baud.

QSerialPort::Baud115200

115200

115200 baud.

4.数据位

设置数据位并保存到帧中,如果设置成功,则返回true,否则返回false,并设置一个错误代码,可以通过访问QSerialPort::error属性的值来获取该代码。

如果在打开端口之前设置了该设置,则实际的串行端口设置将在QSerialPort::open()方法中自动完成,然后端口打开成功。

bool setDataBits(DataBits dataBits);
DataBits dataBits() const;

数据位

5.奇偶校验位

奇偶校验位

bool setParity(Parity parity);
Parity parity() const;

6.设置停止位

停止位

bool setStopBits(StopBits stopBits);
StopBits stopBits() const;

7.发送方式

Hex 16进制,本质上就是将字节数数组转化为16进制,然后用字符串的形式发送出去。

五、对于ui参数随数字变化如何设置

int p = 1;
foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
{
    PortStringList += info.portName();
    ui->port->addItem(info.portName());

    // 构造对象名
    QString objectName = QString("comlist%1").arg(p);

    // 查找对象
    QLineEdit *comlist = this->findChild(objectName);
    if(comlist) {
        comlist->setText(info.portName());
    }

    p++;
}

六、字符串截取

QString snap = QString("COM");
int com_num = com.remove(snap).toInt();

七、源代码

linux与windows端通用多线程串口,可同时检测14个串口。

1.Com_Test.pro

QT       += core gui
QT       += serialport
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++17

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    comthread.cpp \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    comthread.h \
    mainwindow.h

FORMS += \
    mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

2.comthread.h

#ifndef COMTHREAD_H
#define COMTHREAD_H

#include 
#include 
#include 
#include 
#include 

class ComThread :public QObject
{
    Q_OBJECT
public:
    explicit ComThread(QObject *parent = nullptr);
    explicit ComThread(QString portname,qint32 rate,qint32 date,qint32 check_num,qint32 stop_num);
    explicit ComThread(qint32 rate,qint32 date,qint32 check_num,qint32 stop_num);
    explicit ComThread(QString portname);
    // 拷贝构造函数
    explicit ComThread(const ComThread &other);
    ComThread& operator = (const ComThread &other)
    {
        qDebug()<<"进入拷贝";
        if(this != &other)
        {
            qDebug()<<"进入拷贝赋值";
            this->m_com = other.m_com;
            this->mSerialPort = other.mSerialPort;
            this->m_check_num = other.m_check_num;
            this->m_date = other.m_date;
            this->m_rate = other.m_rate;
            this->m_stop_num = other.m_stop_num;

            qDebug()<<"赋值结束";
        }
        qDebug()<<"返回值";
        return *this;
    }

    ~ ComThread();
public slots:
    // 接受传入的串口数据
    void slot_recv_com(QString portname,qint32 rate,qint32 date,qint32 check_num,qint32 stop_num);
    void slot_recv_com_s(qint32 rate,qint32 date,qint32 check_num,qint32 stop_num);
    void slot_recv_com_name(QString portname);
    // 对应主线程的信号,需要的槽函数
    // 1,打开串口槽函数
    // 2,关闭串口槽函数
    void slot_openPort();
    void slot_closePort();
    void slot_handlMessage();
    void serial_signal();
private:
    QSerialPort* mSerialPort;
    QString m_com;
    qint32 m_rate;
    qint32 m_date;
    qint32 m_check_num;
    qint32 m_stop_num;

    QByteArray  Receivetext;
signals:
    // 发送信号

    // 发送字节
    void already_send_byte(QString com ,qint32 num);
    // 发送接受字节
    void already_receive_byte(QString com ,qint32 num);
    // 发送是否成功
    void result(QString com ,qint32 num);  // 0代表失败 1,代表成功
    // 发送接受字符串
    void already_receive_string(QString com ,QString string);
    void send_com_signal(QString com,bool DTRstate,bool DCDstate,bool DSRstate,bool RINGRstate,bool RTSstate,bool CTSstate,bool STDstate,bool SRDstate);

};

#endif // COMTHREAD_H

3.comthread.cpp

#include "comthread.h"

ComThread::ComThread(QObject *parent)
    : QObject{parent}
{
    //    mSerialPort = nullptr;
}

ComThread::ComThread(QString portname)
{
    m_com = portname;
}

ComThread::ComThread(const ComThread &other)
{
//    qDebug()<<"进入赋值拷贝";
    this->m_com = other.m_com;
    this->mSerialPort = other.mSerialPort;
    this->m_check_num = other.m_check_num;
    this->m_date = other.m_date;
    this->m_rate = other.m_rate;
    this->m_stop_num = other.m_stop_num;
}

ComThread::ComThread(QString portname, qint32 rate,qint32 date,qint32 check_num,qint32 stop_num)
{
    m_com = portname;
    m_rate = rate;
    m_date = date;
    m_check_num = check_num;
    m_check_num = stop_num;

}

ComThread::ComThread(qint32 rate, qint32 date, qint32 check_num, qint32 stop_num)
{
    m_rate = rate;
    m_date = date;
    m_check_num = check_num;
    m_check_num = stop_num;
}

ComThread::~ComThread()
{
    if(mSerialPort->isOpen())
    {
        mSerialPort->close();
    }
    delete mSerialPort;
}


void ComThread::slot_recv_com(QString portname, qint32 rate, qint32 date, qint32 check_num, qint32 stop_num)
{
    m_com = portname;
    m_rate = rate;
    m_date = date;
    m_check_num = check_num;
    m_stop_num = stop_num;
}
void ComThread::slot_recv_com_s(qint32 rate, qint32 date, qint32 check_num, qint32 stop_num)
{
    m_rate = rate;
    m_date = date;
    m_check_num = check_num;
    m_stop_num = stop_num;
}

void ComThread::slot_recv_com_name(QString portname)
{
    m_com = portname;
}

void ComThread::slot_openPort()
{
    mSerialPort = new QSerialPort();

   //  qDebug()<<" "<< m_com<<" "<setPortName(m_com);
    mSerialPort->setBaudRate(m_rate);
    switch(m_date)
    {
        //设置对应的数据位
        case5:mSerialPort->setDataBits(QSerialPort::Data5);break;
        case6:mSerialPort->setDataBits(QSerialPort::Data6);break;
        case7:mSerialPort->setDataBits(QSerialPort::Data7);break;
        case8:mSerialPort->setDataBits(QSerialPort::Data8);break;
        // 若刚开始设置为空,端口打开之后再次进行相关的设置
        // 若没有选择就不设置,端口打开之后仍可以设置
        //default:serial->setDataBits(QSerialPort::UnknownDataBits);
    }
    switch(m_check_num)
    {
        case0:mSerialPort->setParity(QSerialPort::NoParity);break;
        case1:mSerialPort->setParity(QSerialPort::EvenParity);break;
        case2:mSerialPort->setParity(QSerialPort::OddParity);break;
        case3:mSerialPort->setParity(QSerialPort::SpaceParity);break;
        case4:mSerialPort->setParity(QSerialPort::MarkParity);break;
            //default:serial->setParity(QSerialPort::UnknownParity);
    }
    switch(m_stop_num)
    {

        case0:mSerialPort->setStopBits(QSerialPort::OneStop);break;
        case1:mSerialPort->setStopBits(QSerialPort::OneAndHalfStop);break;
        case2:mSerialPort->setStopBits(QSerialPort::TwoStop);break;
        //default:serial->setStopBits(QSerialPort::UnknownStopBits);
    }
    // 关联读取信号与槽函数,处理信息
    mSerialPort->setFlowControl(QSerialPort::NoFlowControl);
    connect(mSerialPort,&QSerialPort::readyRead,this,&ComThread::slot_handlMessage);
    qDebug()<<"ComThread slot_openport ThreadID"<< QThread::currentThreadId();
    if(mSerialPort->open(QIODevice::ReadWrite))
    {
        this->serial_signal();
        // 发送数据
        for(int i = 0;i<5;i++)
        {
            QString realbom = QString("RealReal");
            QByteArray send = realbom.toUtf8();
            // qDebug()<write(send);
            emit already_send_byte(m_com,send.length());
            // QThread::msleep(1000);
            mSerialPort->waitForReadyRead(10); // 接受数据

        }
        // qDebug()<<"发送完毕";
        // mSerialPort->waitForReadyRead(100); // 接受数据
        this->slot_closePort();
    }
    else
    {
        //  qDebug()<<"return false";
        // 关闭信号
        emit result(m_com,0);

    };
    if(Receivetext.size() == 0)
    {
        //完全没有接收到数据
        emit result(m_com,0);
    }

}
void ComThread::slot_closePort()
{
    if(mSerialPort->isOpen())
    {
        mSerialPort->close();
    }
}

void ComThread::slot_handlMessage()
{
    qDebug()<<"调用了此信号";
    // mSerialPort->waitForBytesWritten(100);
    Receivetext = mSerialPort->readAll();
    // qDebug()<<"接收:"<pinoutSignals()& QSerialPort::NoSignal; // 没有信号
    // 数据终端准备好
    bool DTRstate = mSerialPort->pinoutSignals()& QSerialPort::DataTerminalReadySignal;
    // 载波信号
    bool DCDstate = mSerialPort->pinoutSignals()& QSerialPort::DataCarrierDetectSignal;
    // 数据准备好
    bool DSRstate = mSerialPort->pinoutSignals()& QSerialPort::DataSetReadySignal;
    //
    bool RINGRstate = mSerialPort->pinoutSignals()& QSerialPort::RingIndicatorSignal;
    // 请求发送
    bool RTSstate = mSerialPort->pinoutSignals()& QSerialPort::RequestToSendSignal;
    // 清除发送
    bool CTSstate = mSerialPort->pinoutSignals()& QSerialPort::ClearToSendSignal;
    // 发送数据
    bool STDstate = mSerialPort->pinoutSignals()& QSerialPort::SecondaryTransmittedDataSignal;
    // 接受数据
    bool SRDstate = mSerialPort->pinoutSignals()& QSerialPort::SecondaryReceivedDataSignal;
    emit send_com_signal(m_com,DTRstate,DCDstate,DSRstate,RINGRstate,RTSstate,CTSstate,STDstate,SRDstate);
    
}

4.mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 
#include "comthread.h"
#include 
//#include 
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow :public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
signals:
    void sig_recv_com(QString portname, qint32 rate, qint32 date, qint32 check_num, qint32 stop_num);
    void sig_recv_com_s(qint32 rate, qint32 date, qint32 check_num, qint32 stop_num);
    void sig_recv_com_name(QString portname);
    void sig_openPort();
    void sig_closePort();
public slots:
    // 接受子线程传回的数据
    void slots_already_send_byte(QString com ,qint32 num);
    // 接受字节
    void slots_already_receive_byte(QString com ,qint32 num);
    // 是否成功
    void slots_result(QString com ,qint32 num);  // 0代表失败 1,代表成功
    // 接受字符串
    void slots_already_receive_string(QString com ,QString string);
    // 接受引脚信号
    void send_com_signal(QString com,bool DTRstate,bool DCDstate,bool DSRstate,bool RINGRstate,bool RTSstate,bool CTSstate,bool STDstate,bool SRDstate);
private slots:

    void on_serial_test_clicked();
protected:

private:
    Ui::MainWindow *ui;
    QStringList PortStringList; // 串口列表
//    QThreadPool *pool = QThreadPool::globalInstance();
    ComThread *comthread;
//    std::vector comthread;
    QThread *mthread;
    QString snap;


};
#endif // MAINWINDOW_H

5.mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //设计循环,将数据放入list列表当中
    int p =1;
    foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
    {
        PortStringList += info.portName();
        ui->port->addItem(info.portName());

        // 构造对象名
        QString objectName = QString("comlist%1").arg(p);
        // 查找对象
        QLineEdit *comlist = this->findChild(objectName);
        if(comlist) {
            comlist->setText(info.portName());
        }
        p++;
    }


    //设置UI页面的默认Index
    ui->bo_num->setCurrentIndex(5);
    ui->data_num->setCurrentIndex(3);
    ui->check_num->setCurrentIndex(2);
    ui->stop_num->setCurrentIndex(0);
}

MainWindow::~MainWindow()
{
    delete ui;
    if(mthread->isRunning())
    {
        mthread->quit();
        mthread->wait();
        delete[] mthread;
//        delete[] comthread;
    }
}

void MainWindow::slots_already_send_byte(QString com, int num)
{
    int com_num = com.remove(snap).toInt();
    QString objectname = QString("send_byte_") + QString::number(com_num);

    QLabel *label = this->findChild(objectname);
    if(label)
    {
        label->setText(QString::number(num));
    }
    qDebug()<findChild(objectname);
    if(label)
    {
        label->setText(QString::number(num));
    }
    qDebug()<findChild(objectname);
    if(button)
    {
        if(1 == num)
        {
            button->setText("通过");
            button->setStyleSheet("background-color:rgb(27,167,132)");
        }
        else
        {
            button->setText("失败");
            button->setStyleSheet("background-color:rgb(238, 72, 99)");
        }
    }
    qDebug()<findChild(objectname);
    if(Edit)
    {
        Edit->setText(string);
    }
    qDebug()<findChild(objectname);
            if(ToolButton)
            {
                ToolButton->setStyleSheet("background-color:rgb(240, 124, 130)");
            }
        }
        if(DCD)
        {
            QString objectname = QString("DCD_") + QString::number(com_num);
            QToolButton *ToolButton = this->findChild(objectname);
            if(ToolButton)
            {
                ToolButton->setStyleSheet("background-color:rgb(240, 124, 130)");
            }
        }
        if(DSR)
        {
            QString objectname = QString("DSR_") + QString::number(com_num);
            QToolButton *ToolButton = this->findChild(objectname);
            if(ToolButton)
            {
                ToolButton->setStyleSheet("background-color:rgb(240, 124, 130)");
            }
        }
        if(RNG)
        {
            QString objectname = QString("RNG_") + QString::number(com_num);
            QToolButton *ToolButton = this->findChild(objectname);
            if(ToolButton)
            {
                ToolButton->setStyleSheet("background-color:rgb(240, 124, 130)");
            }
        }
        if(RTS)
        {
            QString objectname = QString("RTS_") + QString::number(com_num);
            QToolButton *ToolButton = this->findChild(objectname);
            if(ToolButton)
            {
                ToolButton->setStyleSheet("background-color:rgb(240, 124, 130)");
            }
        }
        if(CTS)
        {
            QString objectname = QString("CTS_") + QString::number(com_num);
            QToolButton *ToolButton = this->findChild(objectname);
            if(ToolButton)
            {
                ToolButton->setStyleSheet("background-color:rgb(240, 124, 130)");
            }
        }
        if(STD)
        {
            QString objectname = QString("STD_") + QString::number(com_num);
            QToolButton *ToolButton = this->findChild(objectname);
            if(ToolButton)
            {
                ToolButton->setStyleSheet("background-color:rgb(240, 124, 130)");
            }
        }
        if(SRD)
        {
            QString objectname = QString("SRD_") + QString::number(com_num);
            QToolButton *ToolButton = this->findChild(objectname);
            if(ToolButton)
            {
                ToolButton->setStyleSheet("background-color:rgb(240, 124, 130)");
            }
        }
    }

    qDebug()<<"com"<<"DTRstate"<<"DCDstate"<<"DSRstate"<<"RINGRstate"<<"RTSstate"<<"CTSstate"<<"STDstate"<<"SRDstate";


    qDebug()<port->currentText();
    qint32 rate = ui->bo_num->currentText().toInt();
    qint32 date = ui->data_num->currentText().toInt();
    qint32 check_num = ui->check_num->currentIndex();
    qint32 stop_num = ui->stop_num->currentIndex();
//    qDebug()<<"检查位:"<moveToThread(&mthread[i]);
        mthread[i].start();

        //关联信号
        if(open_test[i])
        {
//            connect(this,&MainWindow::sig_openPort,&comthread[i],&ComThread::slot_openPort);
//            connect(this,&MainWindow::sig_recv_com_s,&comthread[i],&ComThread::slot_recv_com_s);
            connect(this,&MainWindow::sig_openPort,comthread,&ComThread::slot_openPort);
            connect(this,&MainWindow::sig_recv_com_s,comthread,&ComThread::slot_recv_com_s);
            // 关联返回信号
            connect(comthread,&ComThread::already_receive_byte,this,&MainWindow::slots_already_receive_byte);
            connect(comthread,&ComThread::already_receive_string,this,&MainWindow::slots_already_receive_string);
            connect(comthread,&ComThread::already_send_byte,this,&MainWindow::slots_already_send_byte);
            connect(comthread,&ComThread::result,this,&MainWindow::slots_result);
            connect(comthread,&ComThread::send_com_signal,this,&MainWindow::send_com_signal);

            open_test[i] = false;
        }
        qDebug()<<"main ThreaiD:"<start(comthread);
        {
            // 取消单个信号对所有串口的信号连接
//            disconnect(this,&MainWindow::sig_openPort,&comthread[i],&ComThread::slot_openPort);
//            disconnect(this,&MainWindow::sig_recv_com_s,&comthread[i],&ComThread::slot_recv_com_s);
            disconnect(this,&MainWindow::sig_openPort,comthread,&ComThread::slot_openPort);
            disconnect(this,&MainWindow::sig_recv_com_s,comthread,&ComThread::slot_recv_com_s);
        }
        i++;
        // pool->waitForDone();
        //
        qDebug()<<"执行结束";
//        QThread::sleep(1);
        // mthread->quit();
        // mthread[0].isFinished();

    }
    // 循环结束线程
    for(int j = 0;j

6.main.cpp

#include "mainwindow.h"

#include 

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

八、问题解决

1.多线程中出现QObject: Cannot create children for a parent that is in a different thread

这个错误提示的实际意思是,不能在子线程中生成跨线程调用的成员。如果一个成员在父线程中被直接调用了,那么这个成员必须处在父线程中,强行在子线程中生成就会出现这个错误提示。

将在多线程中构造函数中new出来的变量,放到run中执行,因为构造函数是在主线程完成初始化的,线程所属于不同的线程,就会出现此类错误。

解决此问题的关键将线程放到一个地方,例如子线程中的对串口的new定义。

2.munmap_chunk(): invalid pointer解决方案线程池释放,程序崩溃

程序分析:将程序线程池取消自动释放,结果程序确实没有发生崩溃,可以继续运行,所以分析断定,程序崩溃为线程池析构时的问题。

展示关闭线程自动销毁功能,同时程序中多次使用。

解决方案:检查程序结束,或者异常结束资源销毁问题。

3.循环发送信息,并没有收到数据,而是最后一起收到数据

在发送数据之后,加入waitForReadyRead(10);函数,就会接受数据,此函数功能为等待接受,与其说等待,其实立马就接受了数据。否则在测试当中出现了五次循环结束,一同接受数据的情况。

for(int i = 0;i<5;i++)
{
    QString realbom = "realbom";
    QByteArray send = realbom.toUtf8();
    qDebug()<write(send);
//        QThread::sleep(1);
    mSerialPort->waitForReadyRead(10); // 接受数据
}

4.接受数据和发送数据格式不同,接受数据乱码

QByteArray send = realbom.toUtf8();
qDebug()<write(send);
QByteArray  Receivetext = mSerialPort->readAll();
qDebug()<<"接收:"<


来源:瓶子的跋涉内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯