Qt多线程与SocketTCP的简单实现

1.相关说明

多线程实现Qt的socket编程实现客户端发送文件,服务端接收文件,并且在客户端设置了心跳,用于监控服务端是否存活。因为Qt中socket套接字发送数据,会先把数据发送至缓冲区中,在发送数据过程中,socket需要先把发送这个过程做完,才会继续执行下一个过程。所以在发送过程中,服务器挂掉了,客户端还会继续发送,将数据写入缓冲区中,所以这里设置了心跳线程,用于监控服务端。

2.相关界面

客户端界面

服务端界面

 

3.相关代码

客户端

主函数代码

dialog.h

#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QTcpSocket>
QT_BEGIN_NAMESPACE
namespace Ui {
class Dialog;
}
QT_END_NAMESPACE
class Dialog : public QDialog
{
    Q_OBJECT
public:
    Dialog(QWidget *parent = nullptr);
    ~Dialog();
private slots:
    void on_btnSelectFile_clicked();
    void on_btnSendFile_clicked();
    void on_btnCloseConnect_clicked();
signals:
    void startConnect(QString ip, quint16 port);
    void sendFile(QString ip, quint16 port, QString filePath);
    void sendHearbeat(QString ip, quint16 port);
    void closeConnect();
private:
    Ui::Dialog *ui;
    QTcpSocket *m_tcp = nullptr;
};
#endif // DIALOG_H

dialog.cpp

#include "dialog.h"
#include "ui_dialog.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QThread>
#include "tuploadfile.h"
#include "theartbeat.h"

Dialog::Dialog(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::Dialog)
{
    ui->setupUi(this);
    setWindowTitle("客户端");
    // setWindowIcon();
    ui->lineEditIP->setText("127.0.0.1");
    ui->lineEditPort->setText("9999");
    ui->progressBar->setRange(0, 100);
    ui->progressBar->setValue(0);

    // 创建线程
    QThread *threadWork = new QThread;   // 任务线程
    QThread *threadHearbeat = new QThread; // 心跳线程
    // 创建任务对象
    TUploadFile *uploadFile = new TUploadFile;
    uploadFile->moveToThread(threadWork);
    // 创建心跳线程
    THeartbeat *hearbeat = new THeartbeat;
    hearbeat->moveToThread(threadHearbeat);

    // 副线程将socket发送给主线程监管
    connect(uploadFile, &TUploadFile::socketComplete, this, [=](QTcpSocket* tcp, QString ip, quint16 port){
        m_tcp = tcp;
        emit sendHearbeat(ip, port);    //发送心跳信号
    });

    // 发送文件的信号槽
    connect(this, &Dialog::sendFile, uploadFile, &TUploadFile::SendFile);
    connect(uploadFile, &TUploadFile::connect_over, this, [=](){
        // 资源释放
        QMessageBox::information(this, "发送", "发送完成");
    });
    // 进度条的数值变化
    connect(uploadFile, &TUploadFile::curPercent, ui->progressBar, &QProgressBar::setValue);

    // 连接失败或断开
    connect(uploadFile, &TUploadFile::sendInfo, this, [=](QString info){
        QMessageBox::warning(this, "警告", info);
    });

    // 心跳相关
    connect(this, &Dialog::sendHearbeat, hearbeat, &THeartbeat::ConnectServer);
    // connect(hearbeat, &THeartbeat::closeConnect, uploadFile, &TUploadFile::closeConnect, Qt::QueuedConnection);
    connect(hearbeat, &THeartbeat::closeConnect, this, [=](){
        // m_tcp->close();          // 这里会出现跨线程操作socket的错误警告
        // uploadFile->closeConnect();
        uploadFile->SetExitFlag(true); // 这样做会好点
        QMessageBox::warning(this, "警告", "服务器已关闭连接");
    },Qt::AutoConnection);

    // 关闭窗口
    connect(this, &Dialog::destroyed, this, [=](){
        qDebug() << "关闭窗口";
        if(m_tcp != nullptr){
            m_tcp->close();
        }
        threadWork->quit();
        threadWork->wait();
        threadHearbeat->quit();
        threadHearbeat->wait();
        hearbeat->deleteLater();
        uploadFile->deleteLater();
        threadWork->deleteLater();
        threadHearbeat->deleteLater();
    });
    threadWork->start();
    threadHearbeat->start();
}

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

// 选择文件
void Dialog::on_btnSelectFile_clicked()
{
    QString filePath = QFileDialog::getOpenFileName(this, "选择一个文件", QDir::currentPath(), "(*.*)");
    if(filePath.isEmpty()){
        QMessageBox::warning(this, "打开文件", "选择文件不能为空");
        return;
    }
    ui->lineEditSelectFile->setText(filePath);
}

// 发送文件
void Dialog::on_btnSendFile_clicked()
{
    QString ip = ui->lineEditIP->text();
    quint16 port = ui->lineEditPort->text().toUInt();
    qDebug() << "ip:" <<ip << ";port:" << port;
    emit sendFile(ip, port, ui->lineEditSelectFile->text());
}
void Dialog::on_btnCloseConnect_clicked()
{
    if(m_tcp != nullptr){
        m_tcp->close();
    }
    emit closeConnect();
}

心跳

theartbeat.h

#ifndef TUPLOADFILE_H
#define TUPLOADFILE_H

#include <QObject>
#include <QTcpSocket>
#include "theartbeat.h"

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

    void SendFile(QString ip, quint16 port, QString path);
    void SetExitFlag(bool flag);
    void closeConnect();    // 关闭连接
signals:
    void connect_over();                // 发送完成
    void connectOK();                   // 连接成功  占时没用
    void curPercent(int percent);       // 当前发送百分比
    void sendInfo(QString info);        // 统一发送信息
    void socketComplete(QTcpSocket* tcp, QString ip, quint16 port); // tcp句柄发送给主线程监管
private:
    void connectServer(QString ip, quint16 port);
    QString getErrorInfo(int code);     // 获取错误信息
private:
    enum CurrentState {
        SendFileEmpty = 1000,   // 发送文件为空
        ClosingConnect          // 断开连接
    };
    QTcpSocket *m_tcp = nullptr;
    bool exit_flag = false;     // 退出标记
};

#endif // TUPLOADFILE_H

theartbeat.cpp

#include "theartbeat.h"

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


void THeartbeat::ConnectServer(QString ip, quint16 port)
{
    m_tcp = new QTcpSocket;
    m_tcp->connectToHost(QHostAddress(ip), port);
    connect(m_tcp, &QTcpSocket::errorOccurred, this, [=](QAbstractSocket::SocketError err){
        qDebug() << "THeartbeat error:" << err;
    });
    // 断开连接
    connect(m_tcp, &QTcpSocket::disconnected, this, [=](){
        m_tcp->close();
        qDebug() << "THeartbeat 服务端断开连接";
        m_tcp->deleteLater();
        m_tcp = nullptr;
        emit closeConnect();
    });
    // 连接成功信号槽
    connect(m_tcp, &QTcpSocket::connected, this, [=](){
        qDebug() << "THeartbeat 连接服务器成功";
    });
}

主任务线程

tuploadfile.h

#ifndef TUPLOADFILE_H
#define TUPLOADFILE_H

#include <QObject>
#include <QTcpSocket>
#include "theartbeat.h"

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

    void SendFile(QString ip, quint16 port, QString path);
    void SetExitFlag(bool flag);
    void closeConnect();    // 关闭连接
signals:
    void connect_over();                // 发送完成
    void connectOK();                   // 连接成功  占时没用
    void curPercent(int percent);       // 当前发送百分比
    void sendInfo(QString info);        // 统一发送信息
    void socketComplete(QTcpSocket* tcp, QString ip, quint16 port); // tcp句柄发送给主线程监管
private:
    void connectServer(QString ip, quint16 port);
    QString getErrorInfo(int code);     // 获取错误信息
private:
    enum CurrentState {
        SendFileEmpty = 1000,   // 发送文件为空
        ClosingConnect          // 断开连接
    };
    QTcpSocket *m_tcp = nullptr;
    bool exit_flag = false;     // 退出标记
};
#endif // TUPLOADFILE_H

tuploadfile.cpp

#include "tuploadfile.h"
#include <QFile>
#include <QFileInfo>
#include <QDebug>
#include <QAbstractSocket>
#include <QThread>

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

// 析构函数
TUploadFile::~TUploadFile()
{
    if(m_tcp != nullptr){
        m_tcp->deleteLater();
    }
}

void TUploadFile::connectServer(QString ip, quint16 port)
{
    m_tcp = new QTcpSocket;
    QAbstractSocket::SocketState state = m_tcp->state();
    if(QAbstractSocket::UnconnectedState == state){
        qDebug() << "未连接";
    }
    m_tcp->connectToHost(QHostAddress(ip), port);
    state = m_tcp->state();
    qDebug() << "ip:" << ip <<";port:" << port << ";连接状态:" << state;
    connect(m_tcp, &QTcpSocket::errorOccurred, this, [=](QAbstractSocket::SocketError err){
        qDebug() << "TUploadFile error:" << err;
        emit sendInfo(getErrorInfo(err));    // 连接失败或其他错误,若服务器没有打开或连接失败,可以从这里会发出提示
    });
    // 断开连接
    connect(m_tcp, &QTcpSocket::disconnected, this, [=](){
        m_tcp->close();
        qDebug() << "TUploadFile 服务端断开连接";
        m_tcp->deleteLater();
        m_tcp = nullptr;
    });
    this->exit_flag = false;
    emit socketComplete(m_tcp, ip, port);
}

QString TUploadFile::getErrorInfo(int code)
{
    switch(code){
    case QAbstractSocket::ConnectionRefusedError: return QString("连接服务器失败");
    case TUploadFile::SendFileEmpty: return QString("发送文件为空");
    case TUploadFile::ClosingConnect: return QString("发送数据时,断开连接");
        default:
        return QString("");
        }
}

// 发送文件
void TUploadFile::SendFile(QString ip, quint16 port, QString path)
{
    if(path.isEmpty()){
        QString info = getErrorInfo(TUploadFile::SendFileEmpty);
        emit sendInfo(info);
        return;
    }
    // 连接服务器
    this->connectServer(ip, port);
    // 连接成功信号槽
    connect(m_tcp, &QTcpSocket::connected, this, [=](){
        QFile file(path);
        QFileInfo info(path);
        int fileSize = info.size();
        file.open(QFile::ReadOnly);
        int num = 0;
        while(!file.atEnd()){
            QAbstractSocket::SocketState state = m_tcp->state();
            // qDebug() << "state:" << state;
            if(!m_tcp->isValid() || state == QAbstractSocket::ClosingState){
                qDebug() << "发送数据时,断开了连接";
                break;
            }
            if(num == 0){
                m_tcp->write((char*)&fileSize, 4);
            }
            QByteArray line = file.readLine();
            num += line.size();
            int percent = (num*100)/fileSize;
            emit curPercent(percent);
            if(percent == 100){
                emit connect_over();
            }
            m_tcp->write(line);
            // 延迟写入数据,方便操作 50ms
            QThread::msleep(50);
            // 退出标记
            if(this->exit_flag){
                m_tcp->close();
                break;
            }
        }
    });
}

void TUploadFile::SetExitFlag(bool flag)
{
    this->exit_flag = flag;
}

void TUploadFile::closeConnect()
{
    qDebug() << "TUploadFile close";
    m_tcp->close();
}

服务端代码

dialog.h

#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>
#include <QTcpServer>

QT_BEGIN_NAMESPACE
namespace Ui {
class Dialog;
}
QT_END_NAMESPACE

class Dialog : public QDialog
{
    Q_OBJECT

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

private slots:
    void on_btnStartListen_clicked();

    void on_btnCloseListen_clicked();
signals:
    void closeConnect();
private:
    Ui::Dialog *ui;
    QTcpServer *m_server;
};
#endif // DIALOG_H

dialog.cpp

#include "dialog.h"
#include "ui_dialog.h"
#include "recvfile.h"
#include <QMessageBox>

Dialog::Dialog(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::Dialog)
{
    ui->setupUi(this);
    ui->btnCloseListen->setEnabled(false);
    ui->lineEditPort->setText("9999");
    m_server = new QTcpServer(this);
    connect(m_server, &QTcpServer::newConnection, this, [=](){
        // QMessageBox::information(this, "新连接", "有新连接来了");
        QTcpSocket *tcp = m_server->nextPendingConnection();
        // 创建子线程
        RecvFile *recvFileThread = new RecvFile(tcp);
        connect(this, &Dialog::closeConnect, recvFileThread, &RecvFile::closeConnect);
        recvFileThread->start();
        connect(recvFileThread, &RecvFile::over, this, [=](){
            recvFileThread->quit();
            recvFileThread->wait();
            recvFileThread->deleteLater();
            QMessageBox::information(this, "文件接收", "文件接收完毕");
        });
    });
    connect(this, &Dialog::destroyed, this, [=](){
        qDebug() << "服务器窗口关闭";
        m_server->close();
    });

}

Dialog::~Dialog()
{
    delete ui;
}
// 开启监听
void Dialog::on_btnStartListen_clicked()
{
    quint16 port = ui->lineEditPort->text().toUInt();
    qDebug() << "port:" << port;
    m_server->listen(QHostAddress::Any, port);
    ui->btnStartListen->setEnabled(false);
    ui->btnCloseListen->setEnabled(true);
}


void Dialog::on_btnCloseListen_clicked(){
    emit closeConnect();
    m_server->close();
    ui->btnStartListen->setEnabled(true);
}

recvfile.h

#ifndef RECVFILE_H
#define RECVFILE_H

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

class RecvFile : public QThread
{
    Q_OBJECT
public:
    explicit RecvFile(QTcpSocket* tcp = nullptr, QObject *parent = nullptr);
    void closeConnect();
signals:
    void over();
    // QThread interface
protected:
    void run() override;
private:
    int count = 0;
    int total = 0;
    QTcpSocket *m_tcp;
};

#endif // RECVFILE_H

recvfile.cpp

#include "recvfile.h"
#include <QFile>

RecvFile::RecvFile(QTcpSocket *tcp, QObject *parent): QThread{parent}
{
    m_tcp = tcp;
    count = 0;
    total = 0;
    connect(m_tcp, &QTcpSocket::disconnected, this, [=](){
        m_tcp->close();
        qDebug() << "客户端断开连接" ;
    });
}

void RecvFile::closeConnect()
{
    qDebug() << "服务端连接关闭";
    m_tcp->close();
}

void RecvFile::run()
{
    QFile *file = new QFile("recv.txt");
    file->open(QFile::WriteOnly);
    // 接收数据
    connect(m_tcp, &QTcpSocket::readyRead, this, [=](){

        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();
            emit over();
        }
    });
    // 进入事件循环
    exec();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/377331.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

寒假学习第24天---PythonPoc基础编写(二)

提示&#xff1a;所分享内容仅用于每一个爱好者之间的技术讨论及教育目的&#xff0c;所有渗透及工具的使用都需获取授权&#xff0c;禁止用于违法途径&#xff0c;否则需自行承担&#xff0c;本作者不承担相应的后果。 文章目录 前言一、 目标二、过程思路实践开始 总结完整代…

Java基于微信小程序的驾校报名小程序,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

腾讯云游戏服务器购买入口,详细配置精准报价

2024年更新腾讯云游戏联机服务器配置价格表&#xff0c;可用于搭建幻兽帕鲁、雾锁王国等游戏服务器&#xff0c;游戏服务器配置可选4核16G12M、8核32G22M、4核32G10M、16核64G35M、4核16G14M等配置&#xff0c;可以选择轻量应用服务器和云服务器CVM内存型MA3或标准型SA2实例&am…

创建网站的具体步骤是什么?

创建网站的具体步骤是什么 一.领取一个免费域名和SSL证书&#xff0c;和CDN 1.打开网站链接&#xff1a;https://www.rainyun.com/z22_ 2.在网站主页上&#xff0c;您会看到一个"登陆/注册"的选项。 3.点击"登陆/注册"&#xff0c;然后选择"微信登…

假期刷题打卡--Day26

1、MT1212乘法表 请编写一个简单程序&#xff0c;输出九九乘法表。输入n&#xff0c;就输出乘法表到n的地方。 格式 输入格式&#xff1a; 输入整型 输出格式&#xff1a; 输出整型。形式如&#xff1a;1*11 样例 1 输入&#xff1a; 5输出&#xff1a; 1*11 2*12 …

格子表单GRID-FORM | 文档网站搭建(VitePress)与部署(Github Pages)

格子表单/GRID-FORM已在Github 开源&#xff0c;如能帮到您麻烦给个星&#x1f91d; GRID-FORM 系列文章 基于 VUE3 可视化低代码表单设计器嵌套表单与自定义脚本交互文档网站搭建&#xff08;VitePress&#xff09;与部署&#xff08;Github Pages&#xff09; 效果预览 格…

服装设计公司,如何用钉钉实现企业数字化成功转型?

钉钉作为数字化工作平台&#xff0c;为某服装设计公司实现了组织管理的数字化转型&#xff0c;构建了一站式的工作平台。通过钉钉赋能&#xff0c;有利于企业推进组织架构、员工沟通、产品运营和客户服务等方面的数字化、智能化转型。 借助钉钉平台&#xff0c;该服设公司轻松实…

【C++第二阶段】空指针访问成员函数常成员函数常成员属性

你好你好&#xff01; 以下内容仅为当前认识&#xff0c;可能有不足之处&#xff0c;欢迎讨论&#xff01; 文章目录 空指针访问成员函数常成员函数&常成员属性 空指针访问成员函数 类对象类型的空指针可以访问成员函数&#xff0c;但是不能够访问带有成员属性的成员函数。…

js-添加网页快捷方式

title: js-添加网页快捷方式 categories: Javascript tags: [p快捷方式] date: 2024-02-04 15:28:25 comments: false mathjax: true toc: true js-添加网页快捷方式 前篇 谷歌上包困难的情况, 只能通过投放落地页来缓解一下痛苦, web2app 那种形式有几个比较大的缺点就是需要…

人工智能基础部分24-人工智能的数学基础,汇集了人工智能数学知识最全面的概况

、 大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能基础部分24-人工智能的数学基础&#xff0c;汇集了人工智能数学知识最全面的概况&#xff0c;深度学习是一种利用多层神经网络对数据进行特征学习和表示学习的机器学习方法。要全面了解深度学习的数学基…

FlinkSql通用调优策略

历史文章迁移&#xff0c;稍后整理 使用DataGenerator 提前进行压测&#xff0c;了解数据的处理瓶颈、性能测试和消费能力 开启minibatch&#xff1a;"table.exec.mini-batch.enabled", "true" 开启LocalGlobal 两阶段聚合&#xff1a;"table.exec.m…

【力扣】快乐数,哈希集合+快慢指针+数学

快乐数原题地址 方法一&#xff1a;哈希集合 定义函数getNext(n)&#xff0c;返回n的所有位的平方和。一直执行ngetNext(n)&#xff0c;最终只有2种可能&#xff1a; n停留在1。无限循环且不为1。 证明&#xff1a;情况1是存在的&#xff0c;如力扣的示例一&#xff1a; 接…

怎么加密电脑磁盘?磁盘加密软件哪个好?

磁盘是电脑储存数据的基础工具&#xff0c;可以存放大量数据。为了避免数据泄露&#xff0c;可以使用专业的磁盘加密软件加密保护电脑磁盘。那么&#xff0c;磁盘加密软件哪个好呢&#xff1f;下面我们就来了解一下。 磁盘加锁专家 磁盘加锁专家是一款专业的磁盘加锁软件&…

阅读《极客时间 | Kafka核心技术与实战》(一)【Kafka入门】

阅读《极客时间 | Kafka核心技术与实战》 为什么要学习Kafka消息引擎系统ABC一篇文章带你快速搞定Kafka术语我应该选择哪种Kafka&#xff1f;聊聊Kafka的版本号 为什么要学习Kafka 如果你是一名软件开发工程师的话&#xff0c;掌握 Kafka 的第一步就是要根据你掌握的编程语言去…

使用CMSIS-DSP库进行嵌入式音频信号处理

在嵌入式环境下&#xff0c;使用CMSIS-DSP库进行音频信号处理是一种常见的应用场景。通过CMSIS-DSP库&#xff0c;开发人员可以利用嵌入式系统的处理能力来实现各种数字信号处理&#xff08;DSP&#xff09;功能&#xff0c;例如音频滤波、均衡器、噪音消除等。本文将介绍如何在…

C# 中的 out 参数传递

C# 是一种强大的编程语言&#xff0c;它提供了许多功能和特性来帮助开发人员编写高效和可维护的代码。其中&#xff0c;out 参数是 C# 中非常有用的一个特性之一。在本文中&#xff0c;我们将深入探讨 C# 中的 out 参数传递&#xff0c;并介绍它的用法、优势以及一些最佳实践。…

Dataway工具(一个接口竟然可以如此简单的配置出来无需开发任何一行代码,也不需要做任何 Mapping 实体映射绑定。)

基于 DataQL 服务聚合能力&#xff0c;为应用提供的一个接口配置工具&#xff0c;使得使用者无需开发任何代码就配置一个满足需求的接口。整个接口配置、测试、冒烟、发布&#xff0c;一站式都通过 Dataway 提供的 UI 界面完成。UI 会以 Jar 包方式提供并集成到应用中并和应用共…

Qt环境搭建+简单程序实现

Qt是什么 Qt是一个跨平台的C图形用户界面应用程序框架。 框架的本质就是一群大佬发明的让菜鸡写出来的代码也也比较规范 也就是限制程序员的自由&#xff0c;让程序员写出来的代码规范。 库和框架有相似性。 库是被程序员调用的&#xff0c;&#xff08;程序员是主体&…

【自动化测试】---Selenium+Java

1.自动化测试分类 接口自动化测试UI自动化测试&#xff08;移动端自动化测试、Web端自动化测试&#xff09; 2.选择Selenium作为web自动化工具原因&#xff08;面试题&#xff09; 开源免费支持多个浏览器支持多个系统支持多语言Selenium包提供很多供测试使用的API 3.自动化是什…

深入探索 Stable Diffusion:AI图像创新的新纪元

深入探索 Stable Diffusion&#xff1a;AI图像创新的新纪元 介绍 Stable Diffusion 的核心功能和应用场景Stable Diffusion 架构解析深入 Stable Diffusion 的关键组件变分自编码器&#xff08;VAE&#xff09;生成对抗网络&#xff08;GAN&#xff09;注意力机制优化算法数据集…