Qt多線程實現網絡發送文件功能

本文實例為大傢分享瞭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);
}

程序運行結果:

以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: