Qt實現基於多執行緒的檔案傳輸(服務端,客戶端)

進擊的汪sir發表於2021-06-24

1. 效果

先看看效果圖

  • 這是傳輸檔案完成的介面
    在這裡插入圖片描述
  • 客戶端
    在這裡插入圖片描述
  • 服務端
    在這裡插入圖片描述

2. 知識準備

其實檔案傳輸和聊天室十分相似,只不過一個傳輸的是文字,一個傳輸的是檔案,而這方面的知識,我已經在前面的部落格寫過了,不瞭解的同學可以去看一下

還有多執行緒相關的知識

2.1 關於多執行緒

這次是用多執行緒實現的檔案傳輸系統,其中對客戶端來說,子執行緒負責連線伺服器,傳送檔案,主執行緒負責修改進度條,對服務端來說,也是用子執行緒來處理客戶端的請求

2.2 關於檔案傳輸

檔案傳輸採用的是,對客戶端,首先是傳送出整個檔案的大小,需要用到QFileInfo這個類,然後再傳送檔案

對服務端,先接收檔案的大小,然後判斷,當接收的檔案大小等於第一次接收的檔案大小時,停止接收,斷開連線

3. 原始碼

我在程式碼裡面都有非常詳細的註釋,所以就直接放上程式碼啦

3.1 客戶端

標頭檔案 mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

signals:
    void startConnect(unsigned short,QString);
    // 傳送檔案訊號
    void sendFile(QString path);

private slots:
    void on_connectServer_clicked();

    void on_selFile_clicked();

    void on_sendFile_clicked();

 private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

原始檔 mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QThread>
#include "sendfile.h"
#include <QFileDialog>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 設定IP和埠
    ui->ip->setText("127.0.0.1");
    ui->port->setText("8989");

    // 設定進度條
    ui->progressBar->setRange(0,100);
    ui->progressBar->setValue(0);

    // 客戶端在子執行緒中連線伺服器
    // 建立執行緒物件
    QThread* t = new QThread;

    // 建立任務物件
    SendFile* worker = new SendFile;

    // 將worker移動到子執行緒t中
    worker->moveToThread(t);


    // 當傳送sendFile訊號,讓worker的sendFile函式處理(子執行緒)
    connect(this,&MainWindow::sendFile,worker,&SendFile::sendFile);


    // 通過訊號,讓worker開始工作
    // 因為worker 已經移動到了子執行緒中,因此connectServer這個槽函式是在子執行緒中執行的
    connect(this,&MainWindow::startConnect,worker,&SendFile::connectServer);

    // 處理子執行緒傳送的訊號
    // 連線成功
    connect(worker,&SendFile::connectOK,this,[=](){
        QMessageBox::information(this,"連線伺服器","已經成功的連線了伺服器,恭喜!");
    });

    // 斷開連線
    connect(worker,&SendFile::gameover,this,[=](){
        // 資源釋放
        t->quit();
        t->wait();
        worker->deleteLater();
        t->deleteLater();
    });

    connect(worker,&SendFile::curPercent,ui->progressBar,&QProgressBar::setValue);
    // 啟動執行緒
    t->start();
}

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


void MainWindow::on_connectServer_clicked()
{
    QString ip = ui->ip->text();
    unsigned short port = ui->port->text().toUShort();
    emit startConnect(port,ip);

}

void MainWindow::on_selFile_clicked()
{
    QString path = QFileDialog::getSaveFileName();
    // 判斷路徑是否為空
    if(path.isEmpty())
    {
        QMessageBox::warning(this,"開啟檔案","選擇的檔案路徑不能為空");
        return;
    }
    ui->filePath->setText(path);
}



void MainWindow::on_sendFile_clicked()
{
    // 傳送檔案訊號
    emit sendFile(ui->filePath->text());
}

標頭檔案 Send File.h

#ifndef SENDFILE_H
#define SENDFILE_H

#include <QObject>
#include <QTcpSocket>

class SendFile : public QObject
{
    Q_OBJECT
public:
    explicit SendFile(QObject *parent = nullptr);

    // 連線伺服器
    void connectServer(unsigned short port,QString ip);

    // 傳送檔案
    void sendFile(QString path);

signals:
    // 通知主執行緒連線成功
    void connectOK();
    // 通知主執行緒連線成功
    void gameover();
    // 通知主執行緒傳送檔案進度百分比
    void curPercent(int num);


private:
    QTcpSocket* m_tcp;
};

#endif // SENDFILE_H

原始檔SendFile.cpp

#include "sendfile.h"

#include <QFile>
#include <QHostAddress>
#include <QFileInfo>

SendFile::SendFile(QObject* parent) : QObject(parent)
{

}

void SendFile::connectServer(unsigned short port, QString ip)
{
    m_tcp = new QTcpSocket;
    m_tcp->connectToHost(QHostAddress(ip),port);

    // 通知主執行緒連線成功
    connect(m_tcp,&QTcpSocket::connected,this,&SendFile::connectOK);

    // 通知主執行緒斷開連線
    connect(m_tcp,&QTcpSocket::disconnected,this,[=](){
        // 斷開連線,釋放資源
        m_tcp->close();
        m_tcp->deleteLater();
        emit gameover();
    });
}

void SendFile::sendFile(QString path)
{
    QFile file(path);
    // 獲取檔案資訊
    QFileInfo info(path);
    int fileSize = info.size();

    file.open(QFile::ReadOnly);

    // 一行一行的讀檔案
    while(!file.atEnd()){
        static int num = 0;
        // 為了讓伺服器端知道什麼時候停止接收,所以得傳送檔案的大小
        if(num ==0){
            m_tcp->write((char*)&fileSize,4);
        }

        QByteArray line = file.readLine();
        // 計算百分比,發給主執行緒
        num +=line.size();
        int percent =(num*100/fileSize);
        emit curPercent(percent);
        m_tcp->write(line);


    }

}

3.2 服務端

標頭檔案mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTcpServer>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_setListen_clicked();

private:
    Ui::MainWindow *ui;
    QTcpServer* m_s;

};
#endif // MAINWINDOW_H

原始檔maindow.cpp

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

#include <QMessageBox>
#include <QTcpSocket>
#include "recvfile.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    m_s = new QTcpServer(this);


    connect(m_s,&QTcpServer::newConnection,this,[=](){
        QTcpSocket* tcp = m_s->nextPendingConnection();
        // 建立子執行緒,tcp通過引數傳遞
        RecvFile* subThread = new RecvFile(tcp);
        subThread->start();
        connect(subThread,&RecvFile::over,this,[=](){
           subThread->exit();
           subThread->wait();
           subThread->deleteLater();
           QMessageBox::information(this,"檔案接受","檔案接收完畢!!!");
        });
    });
 
}

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


void MainWindow::on_setListen_clicked()
{
    unsigned short port = ui->port->text().toUShort();
    m_s->listen(QHostAddress::Any,port);

}

標頭檔案recvfile.h

#ifndef RECVFILE_H
#define RECVFILE_H

#include <QThread>
#include <QTcpSocket>
class RecvFile : public QThread
{
    Q_OBJECT
public:
    explicit RecvFile(QTcpSocket* tcp,QObject *parent = nullptr);

protected:
    void run() override;

private:
    QTcpSocket* m_tcp;

signals:
    void over();
};

#endif // RECVFILE_H

原始檔recvfile.cpp

#include "recvfile.h"
#include <QFile>
RecvFile::RecvFile(QTcpSocket* tcp,QObject *parent) : QThread(parent)
{
    m_tcp = tcp;
}

void RecvFile::run()
{
    QFile* file = new QFile("recv.txt");
    file->open(QFile::WriteOnly);

    // 接收資料
    connect(m_tcp,&QTcpSocket::readyRead,this,[=](){
        static int count = 0;
        static int total = 0;
        if(count == 0){
            m_tcp->read((char*)&total,4);
        }
        // 讀出剩餘資料
        QByteArray all = m_tcp->readAll();
        count += all.size();
        file->write(all);

        if(count == total){
            m_tcp->close();
            m_tcp->deleteLater();
            file->close();
            file->deleteLater();
            emit over();
        }
    });

    // 進入事件迴圈
    exec();
}

3.4 檔案目錄

在這裡插入圖片描述

4. 結束語

如果有些小夥伴需要工程檔案等,可以聯絡我

相關文章