Qt multithreading realizes the function of sending files over the network

The client sends a file to the server, and the server performs a simple operation of receiving the file

1. server

1. Create an object of the QTcpServer class

QTcpServer * server = new QTcpServer(this);

2. Monitor

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

3. Perform the next step by receiving the newConnection signal sent by QTcpServer

[signal] void QTcpServer::newConnection()

4. Get the socket by calling the nextPendingConnection method

// 通过 this->m_server 调用 nextPendConnection
QTcpSocket * socket = server->nextPendingConnection();

5. Receive the message sent by the client through the [signal] void QIODevice::readyRead() signal

6. Client offline [signal] void QAbstractSocket::disconnected() Signal representation

Create a child thread class, inherit QThread, and override the run() method of the parent class

In the run method, create a file, receive the file sent by the client and write it into the created file;

When receiving a file, first obtain the file size sent by the client for the first time;

Get the file size sent by the client for the first time

// 进行接收数据的时候,需要知道客户端发来的文件的大小
// 先将客户端第一次发来的数据的大小读取出来
static int count = 0;   // 判断是否是客户端第一次发来的数据
static int total = 0;   // 记录文件的大小
        if(count == 0)
        {
            this->m_tcp->read((char*)&total, 4);    // 获取文件大小
        }

Create a child thread class and start the child thread

// 创建子线程类对象
MyQThread * myqtread = new MyQThread;
// 启动子线程
myqtread->start();

Server code:

widget.h main thread header file

#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 main thread:

#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 child thread header file

#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 child thread file

#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. Client

1. Bind ip and port number

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

2. Connect to the server

[signal] void QAbstractSocket::connected()

3. Call the write method through the socket to send a message to the server

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

4. Disconnect

[signal] void QAbstractSocket::disconnected()

Using multithreading to realize selecting files and sending files

Using the second method of multithreading

1. Create a new class and let this class derive from QObject
2. Add a public member function to this new class, the function body is the business logic we want to execute in the child thread
3. Create a in the main thread QThread object, this is the object of the child thread
4. Create a work class object in the main thread
5. To move the work class object to the child thread object, you need to call moveThread provided by the QObject class
6. Start the child thread and call start( ) The thread is started, and the object moved to the thread did not work at that time.
7. Call the object function of the work class to let this function start executing. At this time, it is running in the sub-thread.

Client code:

mythread.h task class header file

#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 task class file

#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 main thread header file

#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);
}

Program running result:

 The benefits of this article, free to receive Qt development learning materials package, technical video, including (C++ language foundation, C++ design pattern, introduction to Qt programming, QT signal and slot mechanism, QT interface development-image drawing, QT network, QT database programming, QT project actual combat, QSS, OpenCV, Quick module, interview questions, etc.) ↓↓↓↓↓↓See below↓↓Click on the bottom of the article to receive the fee↓↓

Guess you like

Origin blog.csdn.net/m0_60259116/article/details/130566630