Qt多线程与SocketTCP的简单实现

1.相关说明

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

2.相关界面

客户端界面

Qt多线程与SocketTCP的简单实现_第1张图片Qt多线程与SocketTCP的简单实现_第2张图片

服务端界面

 

3.相关代码

客户端

主函数代码

dialog.h

#ifndef DIALOG_H
#define DIALOG_H
#include 
#include 
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 
#include 
#include 
#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:" <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 
#include 
#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 
#include 
#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 
#include 
#include 
#include 
#include 

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 
#include 

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 

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 
#include 
#include 

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 

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

你可能感兴趣的:(Qt6,服务器,开发语言,qt6.3)