首頁 > 軟體

Qt多執行緒實現網路傳送檔案功能

2022-08-22 18:03:07

本文範例為大家分享了Qt多執行緒實現網路傳送檔案功能的具體程式碼,供大家參考,具體內容如下

使用者端給伺服器傳送檔案,伺服器進行接收檔案的簡單操作

1. 伺服器

1. 建立QTcpServer 類的物件

QTcpServer * server = new QTcpServer(this);

2. 進行監聽

bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)

3. 通過接收 QTcpServer 發出的 newConnection 的訊號,進行下一步操作

[signal] void QTcpServer::newConnection()

4. 通過呼叫  nextPendingConnection 方法獲取通訊端

// 通過 this->m_server 呼叫 nextPendConnection
QTcpSocket * socket = server->nextPendingConnection();

5. 接收使用者端發來是訊息 通過 [signal] void QIODevice::readyRead() 訊號

6.使用者端下線   [signal] void QAbstractSocket::disconnected() 訊號 表示

建立一個子執行緒類,繼承 QThread ,重寫父類別的run() 方法

在run方法中,建立檔案,接收使用者端發的檔案寫進建立的檔案中;

接收檔案時,要先獲取第一次使用者端發來的檔案大小;

獲取使用者端第一次發來的檔案大小

// 進行接收資料的時候,需要知道使用者端發來的檔案的大小
// 先將使用者端第一次發來的資料的大小讀取出來
static int count = 0;   // 判斷是否是使用者端第一次發來的資料
static int total = 0;   // 記錄檔案的大小
        if(count == 0)
        {
            this->m_tcp->read((char*)&total, 4);    // 獲取檔案大小
        }

建立子執行緒類 並啟動子執行緒

// 建立子執行緒類物件
MyQThread * myqtread = new MyQThread;
// 啟動子執行緒
myqtread->start();

伺服器端程式碼:

widget.h 主執行緒標頭檔案

#ifndef WIDGET_H
#define WIDGET_H
 
#include <QWidget>
#include <QTcpServer>
 
namespace Ui {
class Widget;
}
 
class Widget : public QWidget
{
    Q_OBJECT
public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
private slots:
    void on_listenBtn_clicked();
private:
    // 建立QTcpServer 類的物件
    QTcpServer * m_server;
private:
    Ui::Widget *ui;
};
 
#endif // WIDGET_H

widget.cpp  主執行緒:

#include "widget.h"
#include "ui_widget.h"
 
#include "myqthread.h"
#include <QMessageBox>
 
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
 
    // 設定埠號
    ui->port->setText("8989");
    // 利用多執行緒進行連結伺服器
    // 1. 需要建立一個執行緒類的子類 ,讓其繼承Qt中的執行緒QThread
    // 2. 重寫父類別的run() 方法,在該函數內部編寫子執行緒要處理的具體業務流程
    // 3. 在主執行緒中建立子執行緒物件,new 一個就可以
    // 4. 啟動子執行緒,呼叫start() 方法
    // 範例化QTcpServer 物件
    this->m_server = new QTcpServer(this);
    // 檢驗是否接收使用者端的連線
    connect(this->m_server, &QTcpServer::newConnection, this, [=]()
    {
        // 獲取通訊端
        QTcpSocket * tcp = this->m_server->nextPendingConnection();
        // 建立子執行緒類物件
        MyQThread * myqtread = new MyQThread(tcp);
        // 啟動子執行緒
        myqtread->start();
        // 獲取子執行緒中發來的使用者端埠的訊息
        connect(myqtread, &MyQThread::ClientDisconnect, this, [=]()
        {
            //彈出對話方塊提示
            QMessageBox::warning(this, "警告", "使用者端已斷開連線...");
        });
        // 接收接收完使用者端的訊號
        connect(myqtread, &MyQThread::OverRecveid, this, [=]()
        {
            //彈出對話方塊提示
            QMessageBox::information(this, "提示", "已接收文使用者端發來的資料");
            // 關閉通訊端
            tcp->close();
            // 釋放
            tcp->deleteLater();
            // 釋放執行緒
            myqtread->quit();
            myqtread->wait();
            myqtread->deleteLater();
        });
    });
}
 
Widget::~Widget()
{
    delete ui;
}
// 點選監聽按鈕 進行監聽 按鈕轉到槽的方式
void Widget::on_listenBtn_clicked()
{
    //獲取埠號
    unsigned short port = ui->port->text().toUShort();
    //利用this->m_s 呼叫listen 進行監聽
    this->m_server->listen(QHostAddress::Any, port);
}

myqthread.h 子執行緒標頭檔案

#ifndef MYQTHREAD_H
#define MYQTHREAD_H
 
//#include <QObject>
 
#include <QTcpSocket>
#include <QThread>
 
class MyQThread : public QThread
{
    Q_OBJECT
public:
    explicit MyQThread(QTcpSocket *tcp, QObject *parent = nullptr);
 
    // 2.重寫QThread 類中的受保護成員 run() 方法
protected:
    void run();
 
public:
    // 自定義通訊端物件 記錄主執行緒傳進的通訊端物件 tcp
    QTcpSocket * m_tcp;
 
signals:
    // 自定義訊號 將伺服器接收完使用者端發來的資料 告訴主執行緒
    void OverRecveid();
    // 自定義訊號 將使用者端斷開連線 告訴主執行緒
    void ClientDisconnect();
 
public slots:
};
 
#endif // MYQTHREAD_H

myqthread.cpp 子執行緒檔案

#include "myqthread.h"
 
#include <QFile>
 
MyQThread::MyQThread(QTcpSocket *tcp, QObject *parent) : QThread(parent)
{
    this->m_tcp = tcp;
}
// 2.重寫QThread 類中的受保護成員 run() 方法
void MyQThread::run()
{
    // 1.建立檔案 開啟檔案
    QFile * file = new QFile("recv.txt");
    file->open(QFile::WriteOnly);   // 以只寫的方式開啟檔案
    // 2.檢驗是否進行讀寫
    connect(this->m_tcp, &QTcpSocket::readyRead, this, [=]()
    {
        // 進行接收資料的時候,需要知道使用者端發來的檔案的大小
        // 先將使用者端第一次發來的資料的大小讀取出來
        static int count = 0;   // 判斷是否是使用者端第一次發來的資料
        static int total = 0;   // 記錄檔案的大小
        if(count == 0)
        {
            this->m_tcp->read((char*)&total, 4);    // 獲取檔案大小
        }
        // 將剩下的資料全部讀取出來
        // 獲取使用者端發來的資料
        QByteArray recvClient = this->m_tcp->readAll(); // 全部接收
        // 將讀取的資料的量記錄到count中
        count += recvClient.size();
        // 將資料寫進檔案中
        file->write(recvClient);
        // 判斷伺服器是否把使用者端發來的資料全部讀取完
        if(count == total)
        {
            // 關閉通訊端
            this->m_tcp->close();
            // 釋放通訊端
            this->m_tcp->deleteLater();
            // 關閉檔案
            file->close();
            file->deleteLater();
            // 自定義一個訊號 告訴主執行緒檔案 已接收完畢
            emit OverRecveid();
        }
    });
    // 3.檢驗使用者端是否斷開連線
    connect(m_tcp, &QTcpSocket::disconnected, this, [=]()
    {
        // 將使用者端斷開連線 傳送給主執行緒
        emit this->ClientDisconnect();
    });
    // 呼叫 exec 進入事件迴圈 阻塞
    exec();
}

2.使用者端

1. 繫結 ip 和 埠號

[virtual] void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol)

2. 連線伺服器

[signal] void QAbstractSocket::connected()

3. 通過通訊端 呼叫 write方法傳送訊息給伺服器

qint64 QIODevice::write(const char *data, qint64 maxSize)

4. 斷開連線

[signal] void QAbstractSocket::disconnected()

利用多執行緒實現 選擇檔案 傳送檔案                          

利用第二種多執行緒的方法                                

1.建立一個新的類,讓這個類從QObject中派生                  
2.在這個新的類中新增一個公有的成員函數,函數體是我們要子執行緒中執行的業務邏輯    
3.在主執行緒中建立一個QThread物件,這個就是子執行緒的物件            
4.在主執行緒中建立一個工作類的物件                          
5.將工作類物件移動到子執行緒物件中,需要呼叫QObject類提供的moveThread
6.啟動子執行緒,呼叫start() 這個執行緒啟動了,當時移動到執行緒中的物件並沒有工作
7.呼叫工作類的物件函數,讓這個函數開始執行,這個時候是在移動到那個子執行緒中執行的。       

使用者端程式碼: 

mythread.h 任務類標頭檔案

#ifndef MYTHREAD_H
#define MYTHREAD_H
 
#include <QObject>
#include <QTcpSocket>
 
class MyThread : public QObject
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);
 
    // 連線伺服器
    void connectToServer(unsigned short port, QString ip);
    // 傳送檔案
    void SendFile(QString path);
 
private:
    // 建立QTcpSocket 類的物件
    QTcpSocket * m_socket;
 
signals:
    // 自定義一個資訊 告訴主執行緒 成功連線到伺服器
    void ConnectOK();
 
    // 自定義一個訊號 告訴主執行緒伺服器已斷開連線
    void gameOver();
 
    // 自定義一個訊號 將獲取的百分比傳送給主執行緒
    void SendPercent(int);
 
public slots:
};
 
#endif // MYTHREAD_H

mythread.cpp 任務類檔案

#include "mythread.h"
 
#include <QFileInfo>
#include <QMessageBox>
 
MyThread::MyThread(QObject *parent) : QObject(parent)
{
 
}
// 連線伺服器
void MyThread::connectToServer(unsigned short port, QString ip)
{
    // 範例化socket類的物件
    this->m_socket = new QTcpSocket(this);
    // 嘗試與伺服器取得連線 繫結IP 和埠號
    this->m_socket->connectToHost(ip, port);
    // 檢驗是否成功與伺服器取等連線
    connect(this->m_socket, &QTcpSocket::connected, this, [=]()
    {
        emit this->ConnectOK(); // 自定義一個訊號 告訴主執行緒 成功連線上伺服器
    });
    // 檢驗伺服器是否斷開連線
    connect(this->m_socket, &QTcpSocket::disconnected, this, [=]()
    {
        this->m_socket->close();    // 關閉通訊端
        emit this->gameOver();      // 傳送訊號 告訴主執行緒 伺服器已斷開連線
    });
}
// 傳送檔案
void MyThread::SendFile(QString path)
{
    // 1.獲取檔案大小
    QFileInfo info(path);
    int fileSize = info.size();
    // 2.開啟檔案
    QFile file(path);
    bool ret = file.open(QFile::ReadOnly);
    if(!ret)
    {
        QMessageBox::warning(NULL, "警告", "開啟檔案失敗");
        return; // 退出函數
    }
    // 判斷什麼時候讀完檔案
    while(!file.atEnd())
    {
        // 第一次傳送檔案的時候 將檔案的大小傳送給伺服器
        // 定義一個標記 當標記為0時, 表示第一次傳送檔案
        static int num = 0;
        if(num == 0)
        {
            this->m_socket->write((char*)&fileSize, 4); // 將檔案大小傳送給伺服器
        }
        // 在迴圈體中 每次讀取一行
        QByteArray line = file.readLine();
        // 每次傳送一次資料,就將傳送的資料的量記錄下來 用於更新進度條
        num += line.size();
        // 基於num值 計算百分比
        int percent = (num*100/fileSize);
        // 將百分比傳送給主執行緒
        emit this->SendPercent(percent);
        // 將讀取的資料通過通訊端物件傳送給伺服器
        this->m_socket->write(line);
    }
}

widget.h 主執行緒標頭檔案

#ifndef WIDGET_H
#define WIDGET_H
 
#include <QWidget>
 
namespace Ui {
class Widget;
}
 
class Widget : public QWidget
{
    Q_OBJECT
 
public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
 
signals:
    // 自定義一個訊號 告訴子執行緒進行連結伺服器
    void TellToConnect(unsigned short port, QString ip);
    // 自定義一個訊號 將選中的檔案路徑傳送給任務類
    void SendToFile(QString);
 
private slots:
    void on_connectBtn_clicked();
 
    void on_selectBtn_clicked();
 
    void on_sendBtn_clicked();
 
private:
    QString m_path;
private:
    Ui::Widget *ui;
};
 
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"
 
#include <QFileDialog>
#include <QMessageBox>
#include <QThread>
#include "mythread.h"
 
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
 
    // 利用多執行緒實現 選擇檔案 傳送檔案
    // 利用第二種多執行緒的方法
    // 1.建立一個新的類,讓這個類從QObject中派生
    // 2.在這個新的類中新增一個公有的成員函數,函數體是我們要子執行緒中執行的業務邏輯
    // 3.在主執行緒中建立一個QThread物件,這個就是子執行緒的物件
    // 4.在主執行緒中建立一個工作類的物件
    // 5.將工作類物件移動到子執行緒物件中,需要呼叫QObject類提供的moveThread方法
    // 6.啟動子執行緒,呼叫start() 這個執行緒啟動了,當時移動到執行緒中的物件並沒有工作
    // 7.呼叫工作類的物件函數,讓這個函數開始執行,這個時候是在移動到那個子執行緒中執行的。
 
    // 1.建立QThread物件
    QThread *t = new QThread;
    // 2.建立任務類的物件
    MyThread * working = new MyThread;
    // 3.將任務類物件移動到子執行緒中
    working->moveToThread(t);
    // 啟動子執行緒
    t->start();
    // 4.設定IP 埠號
    ui->ip_lineEide->setText("127.0.0.1");
    ui->port_lineEdit->setText("8989");
    // 5.設定進度條
    ui->progressBar->setRange(0, 100);  // 進度條的範圍
    ui->progressBar->setValue(0);       // 初始化為0
    // 6.更新進度條 通過連線任務類發來的訊號 實現
    connect(working, &MyThread::SendPercent, ui->progressBar, &QProgressBar::setValue);
    // 7.接收任務類發來的成功連線到伺服器 訊號
    connect(working, &MyThread::ConnectOK, this, [=]()
    {
        QMessageBox::information(this, "提示", "成功連線到伺服器");
        // 將檔案按鈕設定成不可用狀態
        ui->sendBtn->setDisabled(false);
    });
    // 8.連線任務類發來的斷開連線的訊號
    connect(working, &MyThread::gameOver, this, [=]()
    {
        QMessageBox::warning(this, "警告", "伺服器已斷開連線");
        //釋放支援
        t->quit();
        t->wait();
        t->deleteLater();
        working->deleteLater();
        // 將檔案按鈕設定成可用狀態
        ui->sendBtn->setDisabled(true);
    });
    // 7.將訊號和工作類物件中的任務函數連線
    connect(this, &Widget::TellToConnect, working, &MyThread::connectToServer);
    // 8.將檔案路徑發給任務函數
    connect(this, &Widget::SendToFile, working, &MyThread::SendFile);
    // 9.將傳送檔案按鈕設定成可用狀態
    ui->sendBtn->setDisabled(true);
}
 
Widget::~Widget()
{
    delete ui;
}
// 連線伺服器
void Widget::on_connectBtn_clicked()
{
    // 獲取ip 和 埠號
    QString ip = ui->ip_lineEide->text();
    unsigned short port = ui->port_lineEdit->text().toShort();
    // 將ip 和 埠號 傳送取出
    emit this->TellToConnect(port, ip);
    // 將傳送檔案按鈕設定成不可用狀態
    ui->sendBtn->setDisabled(false);
}
// 選中檔案
void Widget::on_selectBtn_clicked()
{
    m_path = QFileDialog::getOpenFileName();  // 開啟檔案選擇對話方塊
    // 判斷選中的對話方塊不能為空
    if(m_path.isEmpty())
        QMessageBox::warning(this, "警告", "選中要傳送的檔案不能為空");
    // 將選中的檔案路徑顯示到單行編輯框中
    ui->filePath_lineEdit->setText(m_path);
}
// 傳送檔案
void Widget::on_sendBtn_clicked()
{
    // 將選中的檔案路徑傳送給任務類
    emit this->SendToFile(m_path);
}

程式執行結果:

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com