zoukankan      html  css  js  c++  java
  • Qt-tcp通信

    1  简介

    参考视频:https://www.bilibili.com/video/BV1XW411x7NU?p=56

    参考视频:https://www.bilibili.com/video/BV1XW411x7NU?p=66

    测试1代码github:https://github.com/zhengcixi/Qt_Demo/tree/master/tcp

    测试2代码github:https://github.com/zhengcixi/Qt_Demo/tree/master/tcp_file

    说明:本文介绍Qt下实现tcp客户端和服务器通信的过程。

    Linux下tcp客户端和服务器通信模型可参考我的另一篇博客:https://www.cnblogs.com/mrlayfolk/p/11968446.html。Qt下tcp通信的原理是一样的。

    Qt的TCP通信模型如下:

    (1)TCP服务器端

    1)创建服务器套接字,使用QTcpServer()类;

    2)将套接字设置为监听模式;

    3)等待客户端连接请求,客户端连接上时会触发newConnection信号,可调用nextPendingConnection函数获取客户端的Socket信息;

    4)和客户端进行通信,发送数据可使用write()函数,接收数据可使用read()或readAll函数()。

    (2)TCP客户端

    1)创建套接字;

    2)连接服务器,使用connectToHost()函数;

    3)和服务器进行通信,发送数据可使用write()函数,接收数据可使用read()或readAll函数()。

    2  代码测试

    2.1  基本的tcp通信测试

    功能说明:分别创建两个窗口,一个用作TCP服务器端,一个用作TCP客户端,双方进行通信。窗口如下:

    服务器窗口:                                                                             客户端窗口:

               

    下面分别说明代码实现的步骤:

    (1)服务器端

    首先创建两个套接字指针。tcpserver用作服务器套接字,tcpsocket用作和客户端通信的通信套接字。

    1     QTcpServer *tcpserver = NULL;  //监听套接字
    2     QTcpSocket *tcpsocket = NULL;  //通信套接字

    然后,创建套接字并启动监听。

    1     //监听套接字,指定父对象,自动回收空间
    2     tcpserver = new QTcpServer(this);
    3     //启动监听
    4     tcpserver->listen(QHostAddress::Any, 8888);

    捕捉newConnect信号与槽函数,等待客户端连接:

     1     //等待连接
     2     connect(tcpserver, &QTcpServer::newConnection,
     3         [=]() {
     4             //取出建立好连接的套接字
     5             tcpsocket = tcpserver->nextPendingConnection();
     6             //获取对方的ip的端口
     7             QString ip = tcpsocket->peerAddress().toString();
     8             qint16 port = tcpsocket->peerPort();
     9             QString tmp = QString("[%1:%2]:成功连接").arg(ip).arg(port);
    10             //在当前对话框显示谁和我连接了
    11             ui->textEdit_recv->setText(tmp);
    12         }
    13     );

    发送数据:

    1     //获取编辑区内容
    2     QString str = ui->textEdit_send->toPlainText();
    3     //给对方发送数据
    4     //QString -> char*
    5     tcpsocket->write(str.toUtf8().data());

    接收数据:

    1             //接收数据
    2             connect(tcpsocket, &QTcpSocket::readyRead,
    3                 [=](){
    4                     //从通信套接字中取出内容
    5                      QByteArray array = tcpsocket->readAll();
    6                      ui->textEdit_recv->append(array);
    7                 }
    8             );

    关闭连接:

    1     //主动和客户端断开连接
    2     tcpsocket->disconnectFromHost();
    3     tcpsocket->close();
    4     tcpsocket = NULL;

    (2)客户端

    客户端只需要创建一个套接字,用于和服务器建立连接并通信:

    1     QTcpSocket *tcpsocket = NULL;  //通信套接字
    2     //分配空间,指定父对象
    3     tcpsocket = new QTcpSocket(this);

    和服务器端建立连接:

    1     //获取服务器ip和端口
    2     QString ip = ui->lineEdit_ip->text();
    3     qint16 port = ui->lineEdit_port->text().toInt();
    4     //主动和服务器建立连接
    5     tcpsocket->connectToHost(QHostAddress(ip), port);

    发送数据:

    1     //获取编辑框内容
    2     QString str = ui->textEdit_send->toPlainText();
    3     //发送数据
    4     tcpsocket->write(str.toUtf8().data());

    接收数据:

    1     connect(tcpsocket, &QTcpSocket::readyRead,
    2         [=](){
    3             //获取对方发送的内容
    4             QByteArray array = tcpsocket->readAll();
    5             //追加到编辑区中
    6             ui->textEdit_recv->append(array);
    7         }
    8     );

    断开连接:

    1     //主动和对方断开连接
    2     tcpsocket->disconnectFromHost();
    3     tcpsocket->close();

    (3)完整的工程代码:

    工程所包含的文件有,serverwidget.cpp和serverwidget.h是服务器端的代码,clientwidget.cpp和clientwidget.h是客户端代码。

     serverwidget.cpp代码:

     1 #include "serverwidget.h"
     2 #include "ui_serverwidget.h"
     3 
     4 ServerWidget::ServerWidget(QWidget *parent) :
     5     QWidget(parent),
     6     ui(new Ui::ServerWidget)
     7 {
     8     ui->setupUi(this);
     9     setWindowTitle("服务器: 8888");
    10 
    11     //监听套接字,指定父对象,自动回收空间
    12     tcpserver = new QTcpServer(this);
    13     //启动监听
    14     tcpserver->listen(QHostAddress::Any, 8888);
    15     //等待连接
    16     connect(tcpserver, &QTcpServer::newConnection,
    17         [=]() {
    18             //取出建立好连接的套接字
    19             tcpsocket = tcpserver->nextPendingConnection();
    20             //获取对方的ip的端口
    21             QString ip = tcpsocket->peerAddress().toString();
    22             qint16 port = tcpsocket->peerPort();
    23             QString tmp = QString("[%1:%2]:成功连接").arg(ip).arg(port);
    24             //在当前对话框显示谁和我连接了
    25             ui->textEdit_recv->setText(tmp);
    26 
    27             //接收数据
    28             connect(tcpsocket, &QTcpSocket::readyRead,
    29                 [=](){
    30                     //从通信套接字中取出内容
    31                      QByteArray array = tcpsocket->readAll();
    32                      ui->textEdit_recv->append(array);
    33                 }
    34             );
    35         }
    36     );
    37 }
    38 
    39 ServerWidget::~ServerWidget()
    40 {
    41     delete ui;
    42 }
    43 
    44 
    45 void ServerWidget::on_pushButton_close_clicked()
    46 {
    47     if (NULL == tcpsocket) {
    48         return;
    49     }
    50     //主动和客户端断开连接
    51     tcpsocket->disconnectFromHost();
    52     tcpsocket->close();
    53     tcpsocket = NULL;
    54 }
    55 
    56 void ServerWidget::on_pushButton_send_clicked()
    57 {
    58     if (NULL == tcpsocket) {
    59         return;
    60     }
    61     //获取编辑区内容
    62     QString str = ui->textEdit_send->toPlainText();
    63     //给对方发送数据
    64     //QString -> char*
    65     tcpsocket->write(str.toUtf8().data());
    66 }
    View Code

     serverwidget.h代码:

     1 #ifndef SERVERWIDGET_H
     2 #define SERVERWIDGET_H
     3 
     4 #include <QWidget>
     5 #include <QTcpServer>  //监听套接字
     6 #include <QTcpSocket>  //通信套接字
     7 
     8 namespace Ui {
     9 class ServerWidget;
    10 }
    11 
    12 class ServerWidget : public QWidget
    13 {
    14     Q_OBJECT
    15 
    16 public:
    17     explicit ServerWidget(QWidget *parent = 0);
    18     ~ServerWidget();
    19 
    20 private slots:
    21     void on_pushButton_send_clicked();
    22 
    23     void on_pushButton_close_clicked();
    24 
    25 private:
    26     Ui::ServerWidget *ui;
    27     QTcpServer *tcpserver = NULL;  //监听套接字
    28     QTcpSocket *tcpsocket = NULL;  //通信套接字
    29 };
    30 
    31 #endif // SERVERWIDGET_H
    View Code

    clientwidget.cpp代码:

     1 #include "clientwidget.h"
     2 #include "ui_clientwidget.h"
     3 #include <QHostAddress>
     4 
     5 clientwidget::clientwidget(QWidget *parent) :
     6     QWidget(parent),
     7     ui(new Ui::clientwidget)
     8 {
     9     ui->setupUi(this);
    10     setWindowTitle("客户端");
    11     //分配空间,指定父对象
    12     tcpsocket = new QTcpSocket(this);
    13     //建立连接
    14     connect(tcpsocket, &QTcpSocket::connected,
    15         [=]() {
    16             ui->textEdit_recv->setText("成功和服务器建立了连接");
    17         }
    18     );
    19     //接收数据
    20     connect(tcpsocket, &QTcpSocket::readyRead,
    21         [=](){
    22             //获取对方发送的内容
    23             QByteArray array = tcpsocket->readAll();
    24             //追加到编辑区中
    25             ui->textEdit_recv->append(array);
    26         }
    27     );
    28     //断开连接
    29     connect(tcpsocket, &QTcpSocket::disconnected,
    30         [=](){
    31             ui->textEdit_recv->append("和服务器断开了连接");
    32         }
    33     );
    34 }
    35 
    36 clientwidget::~clientwidget()
    37 {
    38     delete ui;
    39 }
    40 
    41 void clientwidget::on_pushButton_send_clicked()
    42 {
    43     //获取编辑框内容
    44     QString str = ui->textEdit_send->toPlainText();
    45     //发送数据
    46     tcpsocket->write(str.toUtf8().data());
    47 }
    48 
    49 void clientwidget::on_pushButton_close_clicked()
    50 {
    51     //主动和对方断开连接
    52     tcpsocket->disconnectFromHost();
    53     tcpsocket->close();
    54 }
    55 
    56 void clientwidget::on_pushButton_connect_clicked()
    57 {
    58     //获取服务器ip和端口
    59     QString ip = ui->lineEdit_ip->text();
    60     qint16 port = ui->lineEdit_port->text().toInt();
    61     //主动和服务器建立连接
    62     tcpsocket->connectToHost(QHostAddress(ip), port);
    63 }
    View Code

    clientwidget.h代码:

     1 #ifndef CLIENTWIDGET_H
     2 #define CLIENTWIDGET_H
     3 
     4 #include <QWidget>
     5 #include <QTcpSocket>
     6 
     7 namespace Ui {
     8 class clientwidget;
     9 }
    10 
    11 class clientwidget : public QWidget
    12 {
    13     Q_OBJECT
    14 
    15 public:
    16     explicit clientwidget(QWidget *parent = 0);
    17     ~clientwidget();
    18 
    19 private slots:
    20     void on_pushButton_send_clicked();
    21 
    22     void on_pushButton_close_clicked();
    23 
    24     void on_pushButton_connect_clicked();
    25 
    26 private:
    27     Ui::clientwidget *ui;
    28     QTcpSocket *tcpsocket = NULL;
    29 };
    30 
    31 #endif // CLIENTWIDGET_H
    View Code

    main.cpp代码,启动两个窗口:

     1 #include "serverwidget.h"
     2 #include <QApplication>
     3 #include "clientwidget.h"
     4 
     5 int main(int argc, char *argv[])
     6 {
     7     QApplication a(argc, argv);
     8     ServerWidget w;
     9     clientwidget w2;
    10     w.show();
    11     w2.show();
    12 
    13     return a.exec();
    14 }
    View Code

    运行进行测试:

    2.2  使用tcp传输文件

    功能:客户端连接到服务器,服务器端再传输文件给客户端,当文件传输完成时,服务器端等待客户端返回成功接收的信息,然后服务器端关闭客户端的连接。

    通信模型如下:

     有几点需要说明一下:

    (1)服务器端传输文件之前,先传输了一个文件头信息,这个信息是我们自己封装的,格式为:“文件名##文件字节数”,这样客户端就可以根据文件头信息获取文件的信息了,创建相应的文件。

    (2)发送文件数据时,如果文件太大,可以一个一个缓冲区的发送,缓冲区的大小是自己设置的,代码中设置的4kb;

    代码说明如下:

    (1)工程文件有:

    clientwidget.cpp和clientwidget.h是客户端的代码,serverwidget.cpp和serverwidget.h是服务器端的代码。

    (2)直接给出代码,代码中有注释:

    clientwidget.h代码:

     1 #ifndef CLIENTWIDGET_H
     2 #define CLIENTWIDGET_H
     3 
     4 #include <QWidget>
     5 #include <QTcpSocket>
     6 #include <QFile>
     7 
     8 namespace Ui {
     9 class clientWidget;
    10 }
    11 
    12 class clientWidget : public QWidget
    13 {
    14     Q_OBJECT
    15 
    16 public:
    17     explicit clientWidget(QWidget *parent = 0);
    18     ~clientWidget();
    19 
    20 private slots:
    21     void on_pushButton_connect_clicked();
    22 
    23 private:
    24     Ui::clientWidget *ui;
    25 
    26     QTcpSocket *tcpsocket = NULL;  //通信套接字
    27     QFile file;  //文件对象
    28     QString filename; //文件名
    29     qint64 filesize = 0;  //文件大小
    30     qint64 recvsize = 0;  //已发送文件大小
    31     bool isStart; //开始接收文件标志位
    32 };
    33 
    34 #endif // CLIENTWIDGET_H
    View Code

    clientwidget.cpp代码

      1 #include "clientwidget.h"
      2 #include "ui_clientwidget.h"
      3 #include <QDebug>
      4 #include <QMessageBox>
      5 #include <QHostAddress>
      6 #include <QIODevice>
      7 
      8 clientWidget::clientWidget(QWidget *parent) :
      9     QWidget(parent),
     10     ui(new Ui::clientWidget)
     11 {
     12     ui->setupUi(this);
     13     setWindowTitle("客户端");
     14 
     15     isStart = true;
     16     ui->progressBar->setValue(0);  //当前值
     17 
     18     tcpsocket = new QTcpSocket(this);
     19     connect(tcpsocket, &QTcpSocket::readyRead,
     20         [=](){
     21             //取出接收的内容
     22             qDebug() << "isstart:" << isStart;
     23             QByteArray buf = tcpsocket->readAll();
     24             if (isStart == true) {  //接收到头部信息
     25                 isStart = false;
     26                 //解析头部信息  初始化
     27                 filename = QString(buf).section("##", 0, 0);  //文件名
     28                 filesize = QString(buf).section("##", 1, 1).toInt();  //文件大小
     29                 recvsize = 0;   //已经接收文件大小
     30                 qDebug() << "fileName" << filename << "filesize" << filesize;
     31                 //打开文件
     32                 file.setFileName(filename);
     33                 bool isOK = file.open(QIODevice::WriteOnly);
     34                 if (false == isOK) {
     35                     qDebug() << "open error!!!";
     36                     //关闭连接
     37                     tcpsocket->disconnectFromHost();
     38                     tcpsocket->close();
     39                     return;
     40                 }
     41                 //弹出对话框,显示接收文件信息
     42                 QString str = QString("接收的文件:[%1:%2kb]").arg(filename).arg(filesize/1024);
     43                 QMessageBox::information(this, "文件信息", str);
     44                 //设置进度条
     45                 ui->progressBar->setMinimum(0);
     46                 ui->progressBar->setMaximum(filesize/1024);
     47                 ui->progressBar->setValue(0);
     48                 QString str1 = QString("开始接收文件%1,大小为%2bytes").arg(filename).arg(filesize);
     49                 ui->textEdit->append(str1);
     50             } else { //真正的文件信息
     51                 qDebug() << "start write data";
     52                 qint64 len = file.write(buf);
     53                 if (len > 0) {
     54                     recvsize += len;
     55                     qDebug() << len;
     56                 }
     57                 qDebug() << "recvsize" << recvsize << "filesize" << filesize;
     58                 //更新进度条
     59                 ui->progressBar->setValue(recvsize/1024);
     60                 if (recvsize == filesize) {  //文件接收完成
     61                     qDebug() << "recvsize" << recvsize << "filesize" << filesize;
     62                     //先向服务器发送接收文件完成的信息
     63                     tcpsocket->write("file done");
     64                     QMessageBox::information(this, "完成", "文件接收完成");
     65                     file.close();
     66                     tcpsocket->disconnectFromHost();
     67                     tcpsocket->close();
     68                 }
     69             }
     70         }
     71     );
     72     connect(tcpsocket, &QTcpSocket::connected,
     73         [=](){
     74             ui->textEdit->clear();
     75             ui->textEdit->append("已经和服务器建立了连接,等待服务器传输文件...");
     76             ui->pushButton_connect->setEnabled(false);
     77         }
     78     );
     79     connect(tcpsocket, &QTcpSocket::disconnected,
     80         [=](){
     81             ui->textEdit->append("已经和服务器断开了连接");
     82             ui->pushButton_connect->setEnabled(true);
     83         }
     84     );
     85 }
     86 
     87 clientWidget::~clientWidget()
     88 {
     89     delete ui;
     90 }
     91 
     92 void clientWidget::on_pushButton_connect_clicked()
     93 {
     94     //获取服务器的ip和端口
     95     QString ip = ui->lineEdit_ip->text();
     96     quint16 port = ui->lineEdit_port->text().toInt();
     97     //主动和服务器建立连接
     98     tcpsocket->connectToHost(QHostAddress(ip), port);
     99     isStart = true;
    100     //设置进度条
    101     ui->progressBar->setValue(0);
    102 }
    View Code

    serverwidget.h代码:

     1 #ifndef SERVERWIDGET_H
     2 #define SERVERWIDGET_H
     3 
     4 #include <QWidget>
     5 #include <QTcpServer>
     6 #include <QTcpSocket>
     7 #include <QFile>
     8 #include <QTimer>
     9 
    10 namespace Ui {
    11 class ServerWidget;
    12 }
    13 
    14 class ServerWidget : public QWidget
    15 {
    16     Q_OBJECT
    17 
    18 public:
    19     explicit ServerWidget(QWidget *parent = 0);
    20     ~ServerWidget();
    21     void sendData(); //发送文件数据
    22 
    23 
    24 private slots:
    25     void on_pushButton_send_clicked();
    26 
    27     void on_pushButton_choose_clicked();
    28 
    29 private:
    30     Ui::ServerWidget *ui;
    31 
    32     QTcpServer *tcpserver = NULL;  //监听套接字
    33     QTcpSocket *tcpsocket = NULL;  //通信套接字
    34     QFile file;  //文件对象
    35     QString filename; //文件名
    36     qint64 filesize;  //文件大小
    37     qint64 sendsize;  //已发送文件大小
    38     QTimer timer;  //定时器
    39 
    40 };
    41 
    42 #endif // SERVERWIDGET_H
    View Code

    serverwidget.cpp代码:

      1 #include "serverwidget.h"
      2 #include "ui_serverwidget.h"
      3 #include <QFileDialog>
      4 #include <QDebug>
      5 #include <QFileInfo>
      6 #include <QTimer>
      7 
      8 ServerWidget::ServerWidget(QWidget *parent) :
      9     QWidget(parent),
     10     ui(new Ui::ServerWidget)
     11 {
     12     ui->setupUi(this);
     13     setWindowTitle("服务器");
     14 
     15     //监听套接字
     16     tcpserver = new QTcpServer(this);
     17     //启动监听
     18     tcpserver->listen(QHostAddress::Any, 8888);
     19 
     20     //两个按钮都不能使用
     21     ui->pushButton_choose->setEnabled(false);
     22     ui->pushButton_send->setEnabled(false);
     23 
     24     //如果客户端成功和服务器连接
     25     connect(tcpserver, &QTcpServer::newConnection,
     26         [=](){
     27             //取出建立好连接的套接字
     28             tcpsocket = tcpserver->nextPendingConnection();
     29             //获取对方的IP和端口
     30             QString ip = tcpsocket->peerAddress().toString();
     31             quint16 port = tcpsocket->peerPort();
     32             QString str = QString("[%1:%2] 成功连接").arg(ip).arg(port);
     33             ui->textEdit->append(str); //显示到编辑区
     34             //成功连接后,才能选择文件
     35             ui->pushButton_choose->setEnabled(true);
     36             ui->pushButton_send->setEnabled(false);
     37             connect(tcpsocket, &QTcpSocket::readyRead,
     38                 [=]() {
     39                     //取客户端的消息
     40                     QByteArray buf = tcpsocket->readAll();
     41                     if (QString(buf) == "file done") {  //文件接收完毕
     42                         ui->textEdit->append("文件接收完成");
     43                         file.close();
     44                         //断开客户端端口
     45                         tcpsocket->disconnectFromHost();
     46                         tcpsocket->close();
     47                         ui->pushButton_choose->setEnabled(false);
     48                         ui->pushButton_send->setEnabled(false);
     49                     }
     50                 }
     51             );
     52         }
     53     );
     54     //定时器事件
     55     connect(&timer, &QTimer::timeout,
     56         [=](){
     57             //关闭定时器
     58             timer.stop();
     59             //发送数据
     60             sendData();
     61         }
     62     );
     63 
     64 }
     65 
     66 ServerWidget::~ServerWidget()
     67 {
     68     delete ui;
     69 }
     70 
     71 //发送文件按钮
     72 void ServerWidget::on_pushButton_send_clicked()
     73 {
     74     //发送文件头信息
     75     QString head = QString("%1##%2").arg(filename).arg(filesize);
     76     qint64 len = tcpsocket->write(head.toUtf8());
     77     if (len > 0) {  //头部信息发送成功
     78         //防止TCP粘包问题,需要通过定时器样式
     79         timer.start(2000);
     80     } else {
     81         qDebug() << "send header data error!!!";
     82         file.close();
     83         ui->pushButton_choose->setEnabled(true);
     84         ui->pushButton_send->setEnabled(false);
     85     }
     86 }
     87 
     88 //选择文件按钮
     89 void ServerWidget::on_pushButton_choose_clicked()
     90 {
     91     //打开文件
     92     QString filepath = QFileDialog::getOpenFileName(this, "open", "../");
     93     if (false == filepath.isEmpty()) {  //选择文件路径有效
     94         filename.clear();
     95         filesize = 0;
     96         sendsize = 0;
     97         //获取文件信息 文件名 文件大小
     98         QFileInfo info(filepath);
     99         filename = info.fileName();
    100         filesize = info.size();
    101         //只读方式打开文件
    102         file.setFileName(filepath);
    103         bool isOK = file.open(QIODevice::ReadOnly);
    104         if (false == isOK) {
    105             qDebug() << "open file error!!!";
    106         }
    107         QString str = QString("已选择文件:%1").arg(filepath);
    108         ui->textEdit->append(str);
    109         //设置按钮属性
    110         ui->pushButton_choose->setEnabled(false);
    111         ui->pushButton_send->setEnabled(true);
    112     } else {
    113         qDebug() << "选择的文件路径无效!!!";
    114     }
    115 }
    116 
    117 //发送文件数据
    118 void ServerWidget::sendData()
    119 {
    120     ui->textEdit->append("正在发送文件...");
    121     qint64 len = 0;
    122     do {
    123         //每次发送数据的大小
    124         char buf[4*1024] = {0};
    125         //往文件中读数据
    126         len = file.read(buf, sizeof(buf));
    127         if (len <= 0) {
    128             break;
    129         }
    130         //发送数据,读多少,发多少
    131         len = tcpsocket->write(buf, len);
    132         //发送数据大小累加
    133         sendsize += len;
    134         qDebug() << "have send:" << len;
    135     } while (len > 0);
    136 }
    View Code

    main.cpp代码:

     1 #include "serverwidget.h"
     2 #include <QApplication>
     3 #include "clientwidget.h"
     4 
     5 int main(int argc, char *argv[])
     6 {
     7     QApplication a(argc, argv);
     8     ServerWidget w;
     9     clientWidget w2;
    10     w.show();
    11     w2.show();
    12 
    13     return a.exec();
    14 }
    View Code

    运行测试:

    遇到的问题:

    (1)服务器端发送头部信息之后,会定时一段时间,然后再发送文件,主要是为了解决粘包现象(如果不这么做,服务器端发送头部信息和文件时会封装在同一个包中,这样客户端就不能将其区分出来)。视频中的定时时间是20ms,但是如果我们发送的文件太小,测试发现客户端只会接收到一次readyRead信号,无法接收到文件,所以最好把定时器的时间设置大一些。

    存在的bug:

    (1)服务器端发送数据时,发送了头部之后,等待定时器时间到达之后,就会发送文件,是不会管客户端是否准备好了接收,那么当客户端还未准备好接收,但是服务器端已经把文件发送完毕了,这种情况下客户端是不能正确收到文件的。演示效果如下:

    也就是说,我们必须在接收到头部信息之后,服务器发送文件之前客户端把文件创建好。

  • 相关阅读:
    移动硬盘和u盘的区别
    U盘文件系统格式
    WARN: Establishing SSL connection
    数据库基本操作命令
    PCM EQ DRC 音频处理
    那些年、那些歌、那些事
    编译与链接
    Flash芯片你都认识吗?
    ACM暑假集训第三周小结
    H
  • 原文地址:https://www.cnblogs.com/mrlayfolk/p/13283517.html
Copyright © 2011-2022 走看看