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