# Qt 多线程文件传输项目
# 通信流程
# 项目文件
pro文件添加network模块
# 服务端
# mainwindow.h
| #ifndef MAINWINDOW_H | |
| #define MAINWINDOW_H | |
| #include <QMainWindow> | |
| #include "mytcpsever.h" | |
| namespace Ui { | |
| class MainWindow; | |
| } | |
| class MainWindow : public QMainWindow | |
| { | |
| Q_OBJECT | |
| public: | |
| explicit MainWindow(QWidget *parent = nullptr); | |
| ~MainWindow(); | |
| private slots: | |
| void on_start_clicked(); | |
| void on_selectfile_clicked(); | |
| void on_pushButton_clicked(); | |
| signals: | |
| void start(QString name); // 通知子线程可以工作啦 | |
| private: | |
| Ui::MainWindow *ui; | |
| MyTcpSever* m_server; | |
| }; | |
| #endif // MAINWINDOW_H | 
# mainwindow.cpp
| #include "mainwindow.h" | |
| #include "ui_mainwindow.h" | |
| #include <QThread> | |
| #include <QMessageBox> | |
| #include <QFileDialog> | |
| #include <QRandomGenerator> | |
| #include "sendfile.h" | |
| MainWindow::MainWindow(QWidget *parent) : | |
| QMainWindow(parent), | |
| ui(new Ui::MainWindow) | |
| { | |
| ui->setupUi(this); | |
| setWindowTitle("文件发送端"); | |
| ui->ip->setText("127.0.0.1"); | |
| qDebug()<<"当前主线程的Id:" <<QThread::currentThreadId(); | |
| m_server = new MyTcpSever(this); | |
| ui->port->setText("8989"); | |
| connect(m_server,&MyTcpSever::newClient,this,[=](qintptr socket) // 子线程 worker 对象使用 | |
|     { | |
|         // 在 ui 中显示已连接 | |
| ui->status->setText("已连接"); | |
|         // 处理子线程闲逛的动作 | |
| QThread* subThread = new QThread; | |
|         // 添加工作的类,使其工作移动到子线程中去工作 | |
|         // 将 socket 文件内容传递进去 | |
| sendfile* worker = new sendfile(socket); // 不能指定 this 父对象,work 要移动到子线程中,若添加 this 则不可指定 | |
| worker->moveToThread(subThread); | |
| connect(this,&MainWindow::start,worker,&sendfile::working); // 主线程调用 start 信号,子线程就可以调用 working 工作 | |
|         // 当子线程 working 工作完成之后发出一个信号,主线程得到信号之后就可以销毁这些资源 | |
| connect(worker,&sendfile::done,this,[=]() | |
|         { | |
|             // 销毁了子线程,不能继续发送文件啦 | |
| //            qDebug ()<<"销毁子线程和任务对象资源...."; | |
| //            subThread->quit (); // 有些任务还没有完成 | |
| //            subThread->wait (); // 等待完成 | |
| //            subThread->deleteLater (); // 销毁子线程 | |
| //            worker->deleteLater ();  // 也销毁 worker 进程 | |
| //            ui->status->setText ("未连接"); | |
| }); | |
| connect(ui->close,&QPushButton::clicked,this,[=]() | |
|         { | |
|             // 销毁了子线程,不能继续发送文件啦 | |
| qDebug()<<"销毁子线程和任务对象资源...."; | |
| subThread->quit(); // 有些任务还没有完成 | |
| subThread->wait(); // 等待完成 | |
| subThread->deleteLater(); // 销毁子线程 | |
| worker->deleteLater(); // 也销毁 worker 进程 | |
| ui->status->setText("未连接"); | |
| ui->start->setEnabled(true); | |
| }); | |
| connect(worker,&sendfile::text,this,[=](QByteArray msg) | |
|         { | |
|             // 修改每次发送的不同颜色 | |
| QVector<QColor> colors = | |
|             { | |
| Qt::red,Qt::green,Qt::black,Qt::blue,Qt::darkRed,Qt::cyan,Qt::magenta | |
| }; | |
| int index = QRandomGenerator::global()->bounded(colors.size()); // 获取 colors 里面的随机数 | |
|             // 取出某一种颜色 | |
| ui->msg->setTextColor(colors.at(index)); // 设置一种随机颜色 | |
| ui->msg->append(msg); | |
| }); | |
|         // 同步文件发送的总长度 | |
| connect(worker,&sendfile::tot_size_signal,this,[=](qint64 size) | |
|         { | |
|             //qDebug ()<<"发送文件的总大小为"<<size; | |
| ui->progressBar->setMinimum(0); | |
| ui->progressBar->setMaximum(static_cast<int>(size)); | |
| ui->progressBar->setValue(0); // 设置当前值 | |
| }); | |
|         // 同步文件发送的目前长度 | |
| connect(worker,&sendfile::now_size_signal,this,[=](qint64 size) | |
|         { | |
|             //qDebug ()<<"发送当前文件大小"<<size; | |
| ui->progressBar->setValue(static_cast<int>(size)); // 设置当前值 | |
| }); | |
| subThread->start(); // 启动子线程 ,启动之后,woker 不能工作,需要主线程给子线程发送信号,让其子线程工作 | |
| }); | |
| } | |
| MainWindow::~MainWindow() | |
| { | |
| delete ui; | |
| } | |
| void MainWindow::on_start_clicked() | |
| { | |
|     // 绑定端口 | |
| unsigned short port = ui->port->text().toUShort(); | |
| m_server->listen(QHostAddress(ui->ip->text()),port); | |
|     // 通信的套接字对象不可以跨线程访问 | |
| ui->status->setText("已启动服务器"); | |
| ui->start->setEnabled(false); | |
| } | |
| void MainWindow::on_selectfile_clicked() | |
| { | |
| QString path = QFileDialog::getOpenFileName(this);// 获取文件 | |
| if(!path.isEmpty()) | |
|     { | |
|         // 将得到路径设置到 | |
| ui->path->setText(path); | |
|     } | |
| qDebug()<<"用户选中的内容为空"; | |
| } | |
| // 发送文件 | |
| void MainWindow::on_pushButton_clicked() // 发送文件按钮点击之后 | |
| { | |
|     // 得到文件路径 | |
| if(ui->path->text().isEmpty()) | |
|     { | |
| QMessageBox::information(this,"提示","要发送的文件不能为空"); | |
| return; | |
|     } | |
| if(!(ui->status->text() == "已连接")) | |
|     { | |
| QMessageBox::information(this,"提示","还未连接终端或服务器"); | |
| return; | |
|     } | |
| emit start(ui->path->text()); | |
| } | 
# mytcpserver.h
| #ifndef MYTCPSEVER_H | |
| #define MYTCPSEVER_H | |
| #include <QObject> | |
| #include <QTcpServer> | |
| class MyTcpSever : public QTcpServer | |
| { | |
| Q_OBJECT | |
| public: | |
| explicit MyTcpSever(QObject *parent = nullptr); | |
| protected: | |
|     // 虚函数,改写行为 | |
| void incomingConnection(qintptr socketDescriptor); | |
| signals: | |
| void newClient(qintptr socket); | |
| public slots: | |
| }; | |
| #endif // MYTCPSEVER_H | 
# mytcpserver.cpp
| #include "mytcpsever.h" | |
| MyTcpSever::MyTcpSever(QObject *parent) : QTcpServer(parent) | |
| { | |
| } | |
| void MyTcpSever::incomingConnection(qintptr socketDescriptor) | |
| { | |
|     // 将文件描述符发送出去 | |
| emit newClient(socketDescriptor); | |
| } | 
# sendfile.h
| #ifndef SENDFILE_H | |
| #define SENDFILE_H | |
| #include <QObject> | |
| #include <QTcpSocket> | |
| class sendfile : public QObject | |
| { | |
|     Q_OBJECT  //Qt 的信号槽机制 | |
| public: | |
| explicit sendfile(qintptr socket,QObject *parent = nullptr); | |
| void working(QString name); | |
| signals: | |
| void done(); //working 完成之后 | |
| void text(QByteArray msg); // 发送的文件内容 | |
| void tot_size_signal(qint64 size); // 得到文件总长度,发送信号文件总长度 | |
| void now_size_signal(qint64 size); // 每次发送,同步 | |
| public slots: | |
| private: | |
|     qintptr m_socket; | |
| QTcpSocket* m_tcp; | |
| qint64 totalsize; // 记录文件的总长度 | |
| qint64 nowsize; // 记录文件的现有长度 | |
| }; | |
| #endif // SENDFILE_H | 
# sendfile.cpp
| #include "sendfile.h" | |
| #include <QThread> | |
| #include <QDebug> | |
| #include <QFile> | |
| #include <QFileInfo> | |
| #include <QtEndian> | |
| sendfile::sendfile(qintptr socket,QObject *parent) : QObject(parent) | |
| { | |
| m_socket = socket; // 传递的用于通信的套接字,通过此可以与主函数建立连接的套接字通信 | |
| } | |
| void sendfile::working(QString path) | |
| { | |
| qDebug()<<"当前子线程的Id:" <<QThread::currentThreadId(); | |
| m_tcp = new QTcpSocket; | |
| m_tcp->setSocketDescriptor(m_socket); // 设置 socket 文件描述符,m_socket 就可以通信啦 | |
| connect(m_tcp,&QTcpSocket::disconnected,this,[=]() | |
|     { | |
| m_tcp->close(); | |
| m_tcp->deleteLater(); | |
| emit done(); | |
| qDebug() << "客户端数据已经接受完毕,并断开了连接,开始销毁套接字对象,拜拜..."; | |
| }); | |
| qDebug()<<"发送的文件名字:"<<path; | |
| nowsize = 0; | |
|     // 通过 QFile 打开文件 | |
| QFile file(path); | |
|     // 获取文件的总长度 | |
| QFileInfo FileData(path); | |
| totalsize = FileData.size(); | |
| emit tot_size_signal(totalsize); | |
|     // 将文件总大小传过去 | |
| QString size = QString::number(totalsize); | |
| qint64 length = m_tcp->write(size.toUtf8()); | |
| m_tcp->waitForBytesWritten(); | |
| if(length > 0) | |
|     { | |
| QThread::msleep(50); // 休息 | |
|     } | |
| bool bl = file.open(QFile::ReadOnly); | |
| if(bl) | |
|     { | |
| while(!file.atEnd()) // 如果文件没有读完 | |
|         { | |
| QByteArray line = file.readLine(); | |
|             // 添加包头 | |
| int len = qToBigEndian(line.size()); | |
| QByteArray data(reinterpret_cast<char*>(&len), 4); | |
| data.append(line); | |
|             // 发送数据 | |
| m_tcp->write(data); | |
|             // 读取数据 | |
|             // 信号 | |
| emit text(line); | |
|             // 发送目前的 line 量 | |
| nowsize +=line.size(); | |
| emit now_size_signal(nowsize); | |
|             //qDebug ()<<"发送的数据为"<<line; | |
|             // | |
| QThread::msleep(50); // 休息 | |
|         } | |
|     } | |
| file.close(); | |
| } | 
# 客户端
# mainwindow.h
| #ifndef MAINWINDOW_H | |
| #define MAINWINDOW_H | |
| #include <QMainWindow> | |
| #include "recvfile.h" | |
| #include <QThread> | |
| namespace Ui { | |
| class MainWindow; | |
| } | |
| class MainWindow : public QMainWindow | |
| { | |
| Q_OBJECT | |
| public: | |
| explicit MainWindow(QWidget *parent = nullptr); | |
| ~MainWindow(); | |
| private slots: | |
| void on_connect_clicked(); | |
| signals: | |
| void startConnect(QString ip,unsigned short port); | |
| private: | |
| Ui::MainWindow *ui; | |
| QThread* subThread; | |
| RecvFile* worker; | |
| }; | |
| #endif // MAINWINDOW_H | 
# mainwindow.cpp
| #include "mainwindow.h" | |
| #include "ui_mainwindow.h" | |
| #include <QThread> | |
| #include <QMessageBox> | |
| #include <QRandomGenerator> | |
| #include <QDebug> | |
| MainWindow::MainWindow(QWidget *parent) : | |
| QMainWindow(parent), | |
| ui(new Ui::MainWindow) | |
| { | |
| ui->setupUi(this); | |
| setWindowTitle("接收端"); | |
|     // 主线程负责窗口时间,子线程负责文件传输 | |
| qDebug()<<"当前主线程线程ID:"<<QThread::currentThreadId(); | |
|     // 设置默认值 | |
| ui->ip->setText("127.0.0.1"); | |
| ui->port->setText("8989"); | |
|     // 创建子线程 | |
| subThread = new QThread; | |
| worker = new RecvFile; // 不可指定父对象 | |
|     //worker 想工作,除了 subThread start 还需要信号量连接调用 workin | |
| worker->moveToThread(subThread); // 移动到子线程中区 | |
| subThread->start(); // 子线程开始工作 | |
| connect(this,&MainWindow::startConnect,worker,&RecvFile::connectServer); | |
|     // 当子线程连接服务器成功,通知主线程 | |
| connect(worker,&RecvFile::connectOK,this,[=]() | |
|     { | |
|         // 通知程序的使用者 | |
| QMessageBox::information(this,"提示","已经成功连接到了服务器"); | |
| ui->connect->setText("已连接"); | |
| ui->connect->setEnabled(false); | |
| }); | |
|     // 当子线程接收到数据呈现给父进程,将接受的数据呈现到 UI 上面 | |
| connect(worker,&RecvFile::message,this,[=](QByteArray msg) | |
|     { | |
|         // 修改每次发送的不同颜色 | |
| QVector<QColor> colors = | |
|         { | |
| Qt::red,Qt::green,Qt::black,Qt::blue,Qt::darkRed,Qt::cyan,Qt::magenta | |
| }; | |
| int index = QRandomGenerator::global()->bounded(colors.size()); // 获取 colors 里面的随机数 | |
|         // 取出某一种颜色 | |
| ui->msg->setTextColor(colors.at(index)); // 设置一种随机颜色 | |
| ui->msg->append(msg); | |
| }); | |
|     // 当子进程结束 | |
| connect(worker,&RecvFile::gameover,this,[=]() | |
|     { | |
|         // 子线程运行结束,进行资源的释放 | |
| qDebug()<<"子进程文件上传完毕"; | |
| }); | |
| connect(ui->close,&QPushButton::clicked,this,[=]() | |
|     { | |
| subThread->quit(); | |
| subThread->wait(); | |
| subThread->deleteLater(); | |
| ui->connect->setEnabled(true); | |
| ui->connect->setText("连接服务器"); | |
| }); | |
|     // 当子进程更新总文件大小,设置 progressBar | |
| connect(worker,&RecvFile::total_size_signal,this,[=](qint64 size) | |
|     { | |
| qDebug()<<"接受文件总大小为"<<size; | |
| ui->progressBar->setMinimum(0); | |
| ui->progressBar->setMaximum(static_cast<int>(size)); | |
| ui->progressBar->setValue(0); // 设置当前 | |
| }); | |
| connect(worker,&RecvFile::now_size_signal,this,[=](qint64 size) | |
|     { | |
|         //qDebug ()<<"接受文件目前大小为"<<size; | |
| ui->progressBar->setValue(static_cast<int>(size)); // 设置当前 | |
| }); | |
| } | |
| MainWindow::~MainWindow() | |
| { | |
| delete ui; | |
| } | |
| void MainWindow::on_connect_clicked() | |
| { | |
|     // 拿到 IP 端口,将 Ip 和端口传递给子线程,在子线程 new QTcp 套接字对象,在子线程中接受数据 | |
| QString ip =ui->ip->text(); // 读取 ip | |
| unsigned short port = ui->port->text().toUShort(); | |
|     // 通过信号传递给子线程 | |
| emit startConnect(ip,port); | |
| } | 
# recvfile.h
| #ifndef RECVFILE_H | |
| #define RECVFILE_H | |
| #include <QObject> | |
| #include <QTcpSocket> | |
| class RecvFile : public QObject | |
| { | |
| Q_OBJECT | |
| public: | |
| explicit RecvFile(QObject *parent = nullptr); | |
| ~RecvFile(); | |
|     // 连接服务器的函数 | |
| void connectServer(QString ip,unsigned short port); | |
| void dealData(); | |
| signals: | |
| void connectOK(); | |
| void message(QByteArray msg); | |
| void gameover(); | |
| void total_size_signal(qint64); | |
| void now_size_signal(qint64); | |
| public slots: | |
| private: | |
| QTcpSocket* m_tcp; | |
| bool first_total = true; // 首次读取文件总大小 | |
| qint64 nowsize =0; | |
| }; | |
| #endif // RECVFILE_H | 
# recvfile.cpp
| #include "recvfile.h" | |
| #include <QHostAddress> | |
| #include <QDebug> | |
| #include <QThread> | |
| #include <QtEndian> | |
| #include <QDataStream> | |
| RecvFile::RecvFile(QObject *parent) : QObject(parent) | |
| { | |
| } | |
| RecvFile::~RecvFile() | |
| { | |
|     // 销毁套接字 | |
| m_tcp->close(); | |
| m_tcp->deleteLater(); | |
| } | |
| void RecvFile::connectServer(QString ip, unsigned short port) | |
| { | |
| qDebug()<<"当子线程线程ID:"<<QThread::currentThreadId(); | |
|     // 连接服务器 | |
| m_tcp = new QTcpSocket; | |
|     // 连接服务器 | |
| m_tcp->connectToHost(QHostAddress(ip),port); | |
| connect(m_tcp,&QTcpSocket::connected,this,&RecvFile::connectOK); // 成功建立连接,发送连接成功对象 | |
|     // 接受到数据, 服务器发送给客户端数据完整之后 | |
| connect(m_tcp,&QTcpSocket::readyRead,this,[=]() | |
|     { | |
|         // 读取文件总大小 | |
| if(first_total) | |
|         { | |
| first_total = false; | |
| QByteArray array = m_tcp->readLine(); | |
| qDebug()<<"array传入文件大小为:"<<array.data(); | |
| emit total_size_signal(array.toInt()); | |
|         } | |
|         //QByteArray all = m_tcp->readAll(); | |
| //        emit message(all); | |
|         // 对数据进行拆包 | |
| else { | |
| dealData(); | |
| emit gameover(); | |
|         } | |
| }); | |
| } | |
| void RecvFile::dealData() | |
| { | |
| int totalBytes = 0; | |
| int recvBytes = 0; | |
| QByteArray block; // 存储对应的数据块 | |
|     // 判断有没有数据 | |
| if(m_tcp->bytesAvailable() == 0) | |
|     { | |
| qDebug()<<"没有数据咯"; | |
| return ; | |
|     } | |
|     // 读包头 可读的字节大于 4 个字节 // 包头只可以读取字节,无法获取总字节 | |
| if(static_cast<int>(m_tcp->bytesAvailable()) >= static_cast<int>(sizeof (int))) | |
|     { | |
| QByteArray head = m_tcp->read(sizeof(int)); | |
| totalBytes = qFromBigEndian(*reinterpret_cast<int*>(head.data())); | |
|         //qDebug () << "接收方包头的长度:" << totalBytes; | |
|     } | |
| else { | |
| return; // 递归 也会结束 | |
|     } | |
|     // 根据包头去读 数据块 | |
| while(totalBytes-recvBytes >0 &&m_tcp->bytesAvailable() > 0) | |
|     { | |
| block.append( m_tcp->read(totalBytes-recvBytes)); | |
| recvBytes = block.size(); | |
| nowsize +=recvBytes; | |
| emit now_size_signal(nowsize); | |
|     } | |
| if(totalBytes == recvBytes) | |
|     { | |
| emit message(block); | |
|     } | |
|     // 如果还有数据 | |
| if(m_tcp->bytesAvailable() > 0) | |
|     { | |
| qDebug()<<"开始 递归调用...."; | |
| dealData(); | |
|     } | |
| } | 

