Qt多线程与SocketTCP的简单实现

news2025/2/3 15:56:58

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Transformer结构的扩散模型综述

&#x1f380;个人主页&#xff1a; https://zhangxiaoshu.blog.csdn.net &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️&#xff0c;如有错误敬请指正! &#x1f495;未来很长&#xff0c;值得我们全力奔赴更美好的生活&…

【Qt基本功修炼】Qt线程的两种运行模式

1. 前言 QThread是Qt中的线程类&#xff0c;用于实现多线程运行。 QThread有两种工作模式&#xff0c;即 消息循环模式无消息循环模式 两种模式分别适用于不同的场景。下面我们将从多个方面&#xff0c;讲解QThread两种工作模式的区别。 2. 消息循环模式 2.1 实现原理 Q…

Tomcat 部署项目时 war 和 war exploded区别

在 Tomcat 调试部署的时候&#xff0c;我们通常会看到有下面 2 个选项。 是选择war还是war exploded 这里首先看一下他们两个的区别&#xff1a; war 模式&#xff1a;将WEB工程以包的形式上传到服务器 &#xff1b;war exploded 模式&#xff1a;将WEB工程以当前文件夹的位置…

《Pandas 简易速速上手小册》第5章:Pandas 数据合并与重塑(2024 最新版)

文章目录 5.1 数据合并&#xff1a;Concatenate 和 Merge5.1.1 基础知识5.1.2 重点案例&#xff1a;客户订单数据合并5.1.3 拓展案例一&#xff1a;产品目录和销售数据合并5.1.4 拓展案例二&#xff1a;员工信息和部门数据合并 5.2 数据透视和重塑5.2.1 基础知识5.2.2 重点案例…

保姆级系列:各种打印机驱动的安装和使用

保姆级系列&#xff1a;各种打印机驱动的安装和使用 1.介绍2.下载3.安装4.实践教程5.总结 1.介绍 市面上打印机品牌和型号众多&#xff0c;打印机接口目前主要分为如下几种&#xff1a; 逻辑端口&#xff1a;TCP/IP、WSD、USB、LPT、COM 物理端口&#xff1a;RJ45、DB15母、US…

JDK1.8源码环境搭建介绍

目录 一、环境说明 1.1 JDK 1.8 1.2 IDEA 二、搭建过程说明 2.1 创建Java工程 2.2 源码准备 2.2.1 查找源码 2.2.2 解压源码到工程中 2.3 更新SDK 2.3.1 更新工程SDK 2.3.1.1 新建SDK 2.3.1.2 更新工程SDK 2.4 测试 2.4.1 解决报错问题 2.4.1.1 解决 sun.awt.UNI…

C#,桌面游戏编程,数独游戏(Sudoku Game)的算法与源代码

本文包括以下内容&#xff1a; &#xff08;1&#xff09;数独游戏的核心算法&#xff1b; &#xff08;2&#xff09;数独游戏核心算法的源代码&#xff1b; &#xff08;3&#xff09;数独游戏的部分题目样本&#xff1b; &#xff08;4&#xff09;适老版《数独》的设计原则…

Mirus TransIT-X2® 在RNAi干扰实验中性能数据展示

不同RNAi干扰途径示意图 基因沉默相关功能研究在分子和细胞生物学中发挥着重要作用&#xff0c;化学转染也在该研究领域扮演者重要角色。常见参与RNAi干扰途径的天然RNA分子包括&#xff1a; ★.小干扰 RNA (Small interfering RNAs, siRNA) &#xff1a;由双链 RNA(dsRNA)断裂…

第0章 Linux 基础入门

第0章 Linux 基础入门 RHCSA Red Hat Certified System Administrator 红帽认证系统管理员。 什么是计算机 计算机的组成&#xff1a; 控制器 运算器 存储器 输出设备 输入设备 计算机只能识别0和1&#xff0c;也就是二进制数。 为什么要学习Linux Linux 因其高效率…

零基础爬什么值得买的榜单——爬虫练习题目一(答二)

新问题总在解决老问题之后出现 引言原因正文 开整方法一代码运行效果 方法二代码运行结果 推荐 补充两个请求头的参数知识RefererUser-Agent 结尾 引言 今天心情不是很好 但是得更新呀 其实我写博客的一方面 也是希望大家能够监督我 让我尽量少情绪化 保持一个应有的速率做正确…

【VSCode 光标返回上一位置】

默认按键 Windows: Alt ← ;或者 鼠标侧键 Linux: Ctrl Alt - ;貌似数字键盘的减号没效果 Mac: Ctrl - 自定义修改方法&#xff1a; VSCode左下角 “管理 / Manage” “键盘快捷方式 / KeyBoard Shortcuts” 搜索 “前进 / Go Forward 或 后退 / Go Back” 双击需…

PySpark(二)RDD基础、RDD常见算子

目录 RDD RDD五大特性 RDD创建 RDD算子 常见的Transformation算子 map flatMap mapValues reduceByKey groupBy filter distinct union join intersection glom groupByKey groupByKey和reduceByKey的区别 ? sortBy sortByKey 常见的action算子 countByKey…

npm ERR! reason: certificate has expired(淘宝镜像过期)

npm ERR! request to https://registry.npm.taobao.org/yauzl/-/yauzl-2.4.1.tgz failed, reason: certificate has expired 今天在执行npm install命令时&#xff0c;报错百度了下是淘宝证书过期原因 解决方法一 执行下面两个命令再进行npm install即可 npm cache clean --…

LangChain结合通义千问的自建知识库

LangChain结合通义千问的自建知识库 在使用了通义千问API了之后&#xff0c;下一步就是构建知识库文档&#xff0c;使用了比较有名的LangChian&#xff0c;最后成果将自己的txt生成了知识向量库&#xff0c;最后我还把自己的论文生成了一个知识向量库&#xff0c;然后问他我的…

测试环境搭建整套大数据系统(一:基础配置,修改hostname,hosts,免密,时间同步)

一&#xff1a;使用服务器配置。 二&#xff1a;修改服务器名称hostname&#xff0c;hosts。 在 Linux 系统中&#xff0c;hostname 和 /etc/hosts 文件分别用于管理主机名和主机名解析。 在三台服务器上&#xff0c;分别执行以下命令。 vim /etc/hostnamexdso-hadoop-test-0…

Linux内核调参常用整理

一、【写在前面】 Linux内核调参是一个重要知识&#xff0c;这篇文章总结一下常见的参数用法。 调参位置在 /etc/sysctl.conf中或者sysctl.d 区别是&#xff1a; /etc/sysctl.conf 文件&#xff1a; 这是默认的主配置文件&#xff0c;包含了系统上所有的 sysctl 参数配置。所…

maven打包spring项目

常用的Maven命令如下 命令 说明mvn clean 清理Maven 项目。会删除目标路径(一般是target目录)Maven生成的打包文件,编译文件。mvn package 打包Maven项目,会生成jar 或者war文件。mvn test 执行test目录下的测试用例。mvn deploy 发布依赖到远端mvn site 生成…

Leetcode206:反转链表

一、题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表 示例&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]输入&#xff1a;head [] 输出&#xff1…

面试经典 150 题 -- 滑动窗口 (总结)

面试经典150题链接 面试经典 150 题 - 学习计划 - 力扣&#xff08;LeetCode&#xff09;全球极客挚爱的技术成长平台 209 . 长度最小的子数组 思路 : 滑动窗口的思想&#xff0c;取ij0,向后遍历j,记录前缀和[l,r]为s,如果s>target,那么左端点向右移动&#xff0c;直到s…

net 一台路由器如何让两个不同网段的终端可以通信。

# 终端设备自己设置就行了 # 路由器的设置 The device is running! #################################################### <Huawei> Feb 1 2024 21:21:09-08:00 Huawei %%01IFPDT/4/IF_STATE(l)[0]:Interface GigabitEt hernet0/0/0 has turned into UP state. <…