QT多线程实现基于TCP的文件传输功能

写在前面

QT中多线程的两种使用方式:

1、继承QThread类并重写run()函数。

2、使用moveToThread()函数将QObject派生类移动到新的线程中执行。

客户端

客户端UI

客户端主窗口头文件:

#ifndef SENDFILEWINDOW_H
#define SENDFILEWINDOW_H

#include <QMainWindow>
#include <QThread>
namespace Ui {
class SendFileWindow;
}

class SendFileWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit SendFileWindow(QWidget *parent = nullptr);
    ~SendFileWindow();

private slots:
    //连接服务器
    void on_connectBtn_clicked();
    //选择文件
    void on_SelectFilelBtn_clicked();
    //发送文件
    void on_sendFileBtn_clicked();

signals:
    //主函数开始连接服务器信号
    void StartConnect(QString ip, unsigned short port);
    //主线程发送文件信号
    void sendFileSignal(QString filename);
private:
    Ui::SendFileWindow *ui;
};

#endif // SENDFILEWINDOW_H

实现:

#include "sendfilewindow.h"
#include "sendfile.h"
#include "ui_sendfilewindow.h"

#include <QMessageBox>
#include <QFileDialog>

SendFileWindow::SendFileWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::SendFileWindow)
{
    ui->setupUi(this);
    setWindowTitle("发送文件窗口");
    ui->IPlineEdit->setText("127.0.0.1");
    ui->portlineEdit->setText("8080");
    ui->progressBar->setRange(0,100);
    ui->progressBar->setValue(0);

    //线程对象
    QThread * myThread = new QThread;
    //任务对象
    SendFile *task = new SendFile;
    task->moveToThread(myThread); //将任务加入线程

    //主线程发出连接服务器的信号,子线程执行连接服务操作
    connect(this, &SendFileWindow::StartConnect, task, &SendFile::connectServer);
    connect(this, &SendFileWindow::sendFileSignal, task, &SendFile::sendFile);
    //处理子线程发送的信号
    connect(task, &SendFile::connectSuccessfully, this, [=](){
        QMessageBox::information(this, "提示","成功连接服务器");
    });
    connect(task, &SendFile::connectDestroyed, this,[=](){
        myThread->quit();
        myThread->wait();
        task->deleteLater();
        myThread->deleteLater();
    });

    connect(task, &SendFile::GetProgressValue, this->ui->progressBar, &QProgressBar::setValue);
    myThread->start();
}

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

void SendFileWindow::on_connectBtn_clicked()
{
    //点击连接服务器按钮后,主线程发送开始连接服务器的信号
    QString ip = this->ui->IPlineEdit->text();
    unsigned short port = this->ui->portlineEdit->text().toUShort();
    emit StartConnect(ip, port);
}


void SendFileWindow::on_SelectFilelBtn_clicked()
{
    QString filename = QFileDialog::getOpenFileName();
    this->ui->filepath->setText(filename);
}


void SendFileWindow::on_sendFileBtn_clicked()
{
    QString filename = this->ui->filepath->text();
    emit sendFileSignal(filename);
}

子线程负责连接数据库和发送文件

子线程头文件:(应该说是子任务类,不属于线程,只是将子任务加入了子线程中)


#ifndef SENDFILE_H
#define SENDFILE_H


#include <QObject>
#include <QTcpSocket>

class SendFile : public QObject
{
    Q_OBJECT
public:
    explicit SendFile(QObject *parent = nullptr);
    //连接服务器
    void connectServer(QString ip, unsigned short port);
    //发送文件
    void sendFile(QString filePath);

private:
    QTcpSocket *m_socket;
signals:
    //成功连接服务器的信号
    void connectSuccessfully();
    //断开服务器连接信号
    void connectDestroyed();
    //发送进度
    int GetProgressValue(int value);

};

#endif // SENDFILE_H

任务实现:


#include "sendfile.h"

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

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

}

void SendFile::connectServer( QString ip,unsigned short port)
{
    m_socket=new QTcpSocket;
    m_socket->connectToHost(QHostAddress(ip),port);
    //QTcpSocket::connected该信号属于子线程,无法直接在主线程中调用
    //于是通过信号槽机制,定义一个SendFile的信号,将子线程的信号传递给主线程
    connect(m_socket, &QTcpSocket::connected, this, &SendFile::connectSuccessfully);
    connect(m_socket, &QTcpSocket::disconnected, this, [=](){
        m_socket->close();
        m_socket->deleteLater();
        emit connectDestroyed();
    });
}

void SendFile::sendFile(QString filePath)
{
    QFile file(filePath);
    file.open(QFile::ReadOnly);
    QFileInfo fileinfo(filePath);
    int filesize = fileinfo.size();
    int curSize = 0;
    while (!file.atEnd()) {
        //在传输文件之前,第一次向套接字中写入文件大小,以便接收方可以知道需要接收多少数据
        if(curSize==0)
        {
            m_socket->write((char*) &filesize, 4); //用4个字节表示文件大小
        }
        QByteArray line = file.readLine();
        m_socket->write(line); //向套接字中写入数据
        curSize+=line.size();
        int progressValue = (curSize/filesize)*100; //进度值
        emit GetProgressValue(progressValue);
    }
}

服务器端

服务器UI

服务器主窗口头文件:


#ifndef RECEIVEWINDOW_H
#define RECEIVEWINDOW_H

#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>

QT_BEGIN_NAMESPACE
namespace Ui { class receiveWindow; }
QT_END_NAMESPACE

class receiveWindow : public QMainWindow

{
    Q_OBJECT

public:
    receiveWindow(QWidget *parent = nullptr);
    ~receiveWindow();
signals:
    void newSocket(QTcpSocket *socket);

private slots:
    //启动监听
    void on_PortpushButton_clicked();

private:
    Ui::receiveWindow *ui;
    QTcpServer *m_server;
};

#endif // RECEIVEWINDOW_H

实现:


#include "receivewindow.h"
#include "ui_receivewindow.h"
#include <QHostAddress>
#include <QMessageBox>

#include "receivefilethread.h"

receiveWindow::receiveWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::receiveWindow)
{
    ui->setupUi(this);
    this->ui->PortlineEdit->setText("8080");
    m_server= new QTcpServer(this);
    connect(m_server, &QTcpServer::newConnection, this, [=](){
        QTcpSocket *socket = m_server->nextPendingConnection();
        //将在主线程生成的套接字对象传入接收文件的子线程中
        ReceiveFileThread *thread = new ReceiveFileThread;
        connect(this, &receiveWindow::newSocket, thread, &ReceiveFileThread::generateSocket);
        emit newSocket(socket);
        thread->start();
        //接受子线程接收完毕信号
        connect(thread, &ReceiveFileThread::receiveFinished, this, [=](){
            thread->quit();
            thread->wait();
            thread->deleteLater();
            socket->close();
            socket->deleteLater();
            QMessageBox::information(this,"提示","接收完毕");
        });
    });
}

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



void receiveWindow::on_PortpushButton_clicked()
{
    unsigned short port = this->ui->PortlineEdit->text().toUShort();
    m_server->listen(QHostAddress::Any, port);
    QMessageBox::information(this, "提示","服务器已启动");
}

服务器端用子线程接受文件。

子线程头文件:(重载run方法用于启动子线程)


#ifndef RECEIVEFILETHREAD_H
#define RECEIVEFILETHREAD_H


#include <QObject>
#include <QTcpSocket>
#include <QThread>

class ReceiveFileThread : public QThread
{
    Q_OBJECT
public:
    explicit ReceiveFileThread(QObject *parent = nullptr);
    void run() override;
    void generateSocket(QTcpSocket *socket);
private:
    QTcpSocket *m_socket;
signals:
    void receiveFinished();
};

#endif // RECEIVEFILETHREAD_H

子线程实现:


#include "receivefilethread.h"
#include <QFile>
ReceiveFileThread::ReceiveFileThread(QObject *parent)
    : QThread{parent}
{

}

void ReceiveFileThread::generateSocket(QTcpSocket *socket)
{
    this->m_socket=socket;
}

void ReceiveFileThread::run()
{

    //创建文件
    QFile *file= new QFile("receive.txt");
    file->open(QFile::WriteOnly);
    //写入文件
    int total=0;
    int count=0;
    QString name;
    connect(m_socket,&QTcpSocket::readyRead, this,[&](){
        //第一次接收的数据为文件大小
        if(count==0){
            m_socket->read((char *)&total,4);
        }
        if(count==4){
           name= m_socket->readAll();
        }

        QByteArray all = m_socket->readAll();
        count += all.size(); //总大小
        file->write(all);
        //判断数据是否接收完
        if(count==total)
        {
            m_socket->close();
            m_socket->deleteLater();
            file->close();
            file->deleteLater();
            emit receiveFinished(); //发出接收完毕信号给主线程
        }
    });
    //子线程进入循环,防止线程运行完了但是文件没有接收
    exec();

}


  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Qt中使用多线程进行TCP文件传输可以分为以下几个步骤: 1. 创建一个TCP服务器和一个TCP客户端。 2. 在TCP服务器中,为每个连接创建一个线程,并在该线程中处理文件的传输。 3. 在TCP客户端中,创建一个线程,该线程负责发送文件给服务器。 4. 在服务器和客户端之间建立连接后,客户端将文件发送到服务器。 5. 服务器收到文件后,在相应的线程中将文件保存到本地。 下面是一个简单的示例代码,可以供您参考: 1. TCP服务器端代码: ``` void Server::startServer() { tcpServer = new QTcpServer(this); connect(tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection())); if (!tcpServer->listen(QHostAddress::Any, 12345)) { qDebug() << "Unable to start the server: " << tcpServer->errorString(); return; } qDebug() << "Server is running on port " << tcpServer->serverPort(); } void Server::newConnection() { QTcpSocket* clientSocket = tcpServer->nextPendingConnection(); ServerThread* thread = new ServerThread(clientSocket, this); connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); thread->start(); } ``` 2. TCP服务器端线程代码: ``` ServerThread::ServerThread(qintptr socketDescriptor, QObject *parent) : QThread(parent), socketDescriptor(socketDescriptor) { } void ServerThread::run() { QTcpSocket tcpSocket; if (!tcpSocket.setSocketDescriptor(socketDescriptor)) { return; } QFile file("/path/to/save/file"); if (!file.open(QIODevice::WriteOnly)) { return; } QByteArray buffer; while (tcpSocket.bytesAvailable() > 0) { buffer = tcpSocket.read(1024); file.write(buffer); } file.close(); } ``` 3. TCP客户端代码: ``` void Client::sendFile(QString filePath) { QThread* thread = new QThread(); ClientWorker* worker = new ClientWorker(filePath); worker->moveToThread(thread); connect(thread, SIGNAL(started()), worker, SLOT(sendFile())); connect(worker, SIGNAL(finished()), thread, SLOT(quit())); connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater())); connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); thread->start(); } ``` 4. TCP客户端线程代码: ``` ClientWorker::ClientWorker(QString filePath) : filePath(filePath) { } void ClientWorker::sendFile() { QTcpSocket tcpSocket; tcpSocket.connectToHost("127.0.0.1", 12345); if (!tcpSocket.waitForConnected()) { return; } QFile file(filePath); if (!file.open(QIODevice::ReadOnly)) { return; } QByteArray buffer; while (!file.atEnd()) { buffer = file.read(1024); tcpSocket.write(buffer); } file.close(); } ``` 请注意,这只是一个简单的示例代码,您需要根据您的实际需求进行修改和优化。同时,确保您的代码具有足够的安全性和可靠性,以避免数据丢失和安全问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值