写在前面
QT中多线程的两种使用方式:
1、继承QThread类并重写run()函数。
2、使用moveToThread()函数将QObject派生类移动到新的线程中执行。
客户端
客户端UI
客户端主窗口头文件:
#ifndef SENDFILEWINDOW_H
#define SENDFILEWINDOW_H
#include <QMainWindow>
#include <QThread>
namespace Ui {
class SendFileWindow;
}
class SendFileWindow : public QMainWindow
{
Q_OBJECT
public:
explicit SendFileWindow(QWidget *parent = nullptr);
~SendFileWindow();
private slots:
//连接服务器
void on_connectBtn_clicked();
//选择文件
void on_SelectFilelBtn_clicked();
//发送文件
void on_sendFileBtn_clicked();
signals:
//主函数开始连接服务器信号
void StartConnect(QString ip, unsigned short port);
//主线程发送文件信号
void sendFileSignal(QString filename);
private:
Ui::SendFileWindow *ui;
};
#endif // SENDFILEWINDOW_H
实现:
#include "sendfilewindow.h"
#include "sendfile.h"
#include "ui_sendfilewindow.h"
#include <QMessageBox>
#include <QFileDialog>
SendFileWindow::SendFileWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::SendFileWindow)
{
ui->setupUi(this);
setWindowTitle("发送文件窗口");
ui->IPlineEdit->setText("127.0.0.1");
ui->portlineEdit->setText("8080");
ui->progressBar->setRange(0,100);
ui->progressBar->setValue(0);
//线程对象
QThread * myThread = new QThread;
//任务对象
SendFile *task = new SendFile;
task->moveToThread(myThread); //将任务加入线程
//主线程发出连接服务器的信号,子线程执行连接服务操作
connect(this, &SendFileWindow::StartConnect, task, &SendFile::connectServer);
connect(this, &SendFileWindow::sendFileSignal, task, &SendFile::sendFile);
//处理子线程发送的信号
connect(task, &SendFile::connectSuccessfully, this, [=](){
QMessageBox::information(this, "提示","成功连接服务器");
});
connect(task, &SendFile::connectDestroyed, this,[=](){
myThread->quit();
myThread->wait();
task->deleteLater();
myThread->deleteLater();
});
connect(task, &SendFile::GetProgressValue, this->ui->progressBar, &QProgressBar::setValue);
myThread->start();
}
SendFileWindow::~SendFileWindow()
{
delete ui;
}
void SendFileWindow::on_connectBtn_clicked()
{
//点击连接服务器按钮后,主线程发送开始连接服务器的信号
QString ip = this->ui->IPlineEdit->text();
unsigned short port = this->ui->portlineEdit->text().toUShort();
emit StartConnect(ip, port);
}
void SendFileWindow::on_SelectFilelBtn_clicked()
{
QString filename = QFileDialog::getOpenFileName();
this->ui->filepath->setText(filename);
}
void SendFileWindow::on_sendFileBtn_clicked()
{
QString filename = this->ui->filepath->text();
emit sendFileSignal(filename);
}
子线程负责连接数据库和发送文件
子线程头文件:(应该说是子任务类,不属于线程,只是将子任务加入了子线程中)
#ifndef SENDFILE_H
#define SENDFILE_H
#include <QObject>
#include <QTcpSocket>
class SendFile : public QObject
{
Q_OBJECT
public:
explicit SendFile(QObject *parent = nullptr);
//连接服务器
void connectServer(QString ip, unsigned short port);
//发送文件
void sendFile(QString filePath);
private:
QTcpSocket *m_socket;
signals:
//成功连接服务器的信号
void connectSuccessfully();
//断开服务器连接信号
void connectDestroyed();
//发送进度
int GetProgressValue(int value);
};
#endif // SENDFILE_H
任务实现:
#include "sendfile.h"
#include <QFile>
#include <QFileInfo>
#include <QHostAddress>
SendFile::SendFile(QObject *parent)
: QObject{parent}
{
}
void SendFile::connectServer( QString ip,unsigned short port)
{
m_socket=new QTcpSocket;
m_socket->connectToHost(QHostAddress(ip),port);
//QTcpSocket::connected该信号属于子线程,无法直接在主线程中调用
//于是通过信号槽机制,定义一个SendFile的信号,将子线程的信号传递给主线程
connect(m_socket, &QTcpSocket::connected, this, &SendFile::connectSuccessfully);
connect(m_socket, &QTcpSocket::disconnected, this, [=](){
m_socket->close();
m_socket->deleteLater();
emit connectDestroyed();
});
}
void SendFile::sendFile(QString filePath)
{
QFile file(filePath);
file.open(QFile::ReadOnly);
QFileInfo fileinfo(filePath);
int filesize = fileinfo.size();
int curSize = 0;
while (!file.atEnd()) {
//在传输文件之前,第一次向套接字中写入文件大小,以便接收方可以知道需要接收多少数据
if(curSize==0)
{
m_socket->write((char*) &filesize, 4); //用4个字节表示文件大小
}
QByteArray line = file.readLine();
m_socket->write(line); //向套接字中写入数据
curSize+=line.size();
int progressValue = (curSize/filesize)*100; //进度值
emit GetProgressValue(progressValue);
}
}
服务器端
服务器UI
服务器主窗口头文件:
#ifndef RECEIVEWINDOW_H
#define RECEIVEWINDOW_H
#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class receiveWindow; }
QT_END_NAMESPACE
class receiveWindow : public QMainWindow
{
Q_OBJECT
public:
receiveWindow(QWidget *parent = nullptr);
~receiveWindow();
signals:
void newSocket(QTcpSocket *socket);
private slots:
//启动监听
void on_PortpushButton_clicked();
private:
Ui::receiveWindow *ui;
QTcpServer *m_server;
};
#endif // RECEIVEWINDOW_H
实现:
#include "receivewindow.h"
#include "ui_receivewindow.h"
#include <QHostAddress>
#include <QMessageBox>
#include "receivefilethread.h"
receiveWindow::receiveWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::receiveWindow)
{
ui->setupUi(this);
this->ui->PortlineEdit->setText("8080");
m_server= new QTcpServer(this);
connect(m_server, &QTcpServer::newConnection, this, [=](){
QTcpSocket *socket = m_server->nextPendingConnection();
//将在主线程生成的套接字对象传入接收文件的子线程中
ReceiveFileThread *thread = new ReceiveFileThread;
connect(this, &receiveWindow::newSocket, thread, &ReceiveFileThread::generateSocket);
emit newSocket(socket);
thread->start();
//接受子线程接收完毕信号
connect(thread, &ReceiveFileThread::receiveFinished, this, [=](){
thread->quit();
thread->wait();
thread->deleteLater();
socket->close();
socket->deleteLater();
QMessageBox::information(this,"提示","接收完毕");
});
});
}
receiveWindow::~receiveWindow()
{
delete ui;
}
void receiveWindow::on_PortpushButton_clicked()
{
unsigned short port = this->ui->PortlineEdit->text().toUShort();
m_server->listen(QHostAddress::Any, port);
QMessageBox::information(this, "提示","服务器已启动");
}
服务器端用子线程接受文件。
子线程头文件:(重载run方法用于启动子线程)
#ifndef RECEIVEFILETHREAD_H
#define RECEIVEFILETHREAD_H
#include <QObject>
#include <QTcpSocket>
#include <QThread>
class ReceiveFileThread : public QThread
{
Q_OBJECT
public:
explicit ReceiveFileThread(QObject *parent = nullptr);
void run() override;
void generateSocket(QTcpSocket *socket);
private:
QTcpSocket *m_socket;
signals:
void receiveFinished();
};
#endif // RECEIVEFILETHREAD_H
子线程实现:
#include "receivefilethread.h"
#include <QFile>
ReceiveFileThread::ReceiveFileThread(QObject *parent)
: QThread{parent}
{
}
void ReceiveFileThread::generateSocket(QTcpSocket *socket)
{
this->m_socket=socket;
}
void ReceiveFileThread::run()
{
//创建文件
QFile *file= new QFile("receive.txt");
file->open(QFile::WriteOnly);
//写入文件
int total=0;
int count=0;
QString name;
connect(m_socket,&QTcpSocket::readyRead, this,[&](){
//第一次接收的数据为文件大小
if(count==0){
m_socket->read((char *)&total,4);
}
if(count==4){
name= m_socket->readAll();
}
QByteArray all = m_socket->readAll();
count += all.size(); //总大小
file->write(all);
//判断数据是否接收完
if(count==total)
{
m_socket->close();
m_socket->deleteLater();
file->close();
file->deleteLater();
emit receiveFinished(); //发出接收完毕信号给主线程
}
});
//子线程进入循环,防止线程运行完了但是文件没有接收
exec();
}