多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”[1]
需要在工程文件pro中添加network库
QT += core gui network
要使用lambda函数的话还需在工程文件在加入
CONFIG += C++11
创建一个工作类继承QObject类
接收文件的.h文件
#ifndef RECVFILE_H
#define RECVFILE_H
#include
#include
#include
#include
class RecvFile : public QObject
{
Q_OBJECT
public:
explicit RecvFile(QObject *parent = nullptr);
//接收文件函数
void recvFile();
//启动监听
void startListen(unsigned short port);
signals:
//接收完毕
void recvOver();
//通知主线程发送文件进度百分比
void curPercent(int num);
public slots:
private:
QTcpServer *tcpServer;//监听套接字
QTcpSocket *tcpSocket;
QFile file;
QString filename;//文件名称
qint16 filesize;//文件大小
qint16 recvfilesize;//已接收大小
bool isStart;//接收头部标记
};
#endif // RECVFILE_H
接收文件的.cpp文件
#include "recvfile.h"
#include
#include
RecvFile::RecvFile(QObject *parent) : QObject(parent)
{
}
void RecvFile::startListen(unsigned short port)
{
qDebug()<<port;
//分配内存空间
tcpServer = new QTcpServer(this);
tcpServer->listen(QHostAddress::Any,port);
connect(tcpServer,&QTcpServer::newConnection,[=]()
{
//取出建立好连接的套接字
tcpSocket = tcpServer->nextPendingConnection();
isStart = true;
connect(tcpSocket,&QTcpSocket::readyRead,this,&RecvFile::recvFile);
connect(tcpSocket,&QTcpSocket::disconnected,this,[=]()
{
//断开连接
tcpSocket->close();
tcpSocket->deleteLater();
emit recvOver();
});
});
}
void RecvFile::recvFile()
{
//取出接收的内容
QByteArray buf = tcpSocket->readAll();
if(true == isStart)
{
//接收头部
isStart = false;
//解析头部
filename = QString(buf).section("##",0,0);
filesize = QString(buf).section("##",1,1).toInt();
recvfilesize = 0;
//打开文件
file.setFileName(filename);
bool isOk = file.open(QIODevice::WriteOnly);
if(false == isOk)
{
return;
}
}
else
{
qint64 len = file.write(buf);
if(len>0)
{
recvfilesize += len;//累计接收大小
int percent = (static_cast<int>(recvfilesize)*100)/filesize;
//发出更新进度条的信号
emit curPercent(percent);
}
if(recvfilesize == filesize)
{
file.close();
isStart = true;
}
}
}
主窗口的.h文件
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include
#include
namespace Ui {
class MyWidget;
}
class MyWidget : public QWidget
{
Q_OBJECT
public:
explicit MyWidget(QWidget *parent = 0);
~MyWidget();
signals:
void startListen(unsigned short);
private slots:
void on_buttonlisten_clicked();
private:
Ui::MyWidget *ui;
QTcpServer *tcpServer;
};
#endif // MYWIDGET_H
主窗口的.cpp文件
#include "mywidget.h"
#include "ui_mywidget.h"
#include
#include
#include
#include "recvfile.h"
MyWidget::MyWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::MyWidget)
{
ui->setupUi(this);
//设置进度条
ui->progressBar->setRange(0,100);
ui->progressBar->setValue(0);
//创建线程对象
QThread *thread = new QThread;
//创建任务对象
RecvFile *recvWork = new RecvFile;
//将任务对象移到子线程中
recvWork->moveToThread(thread);
connect(this,&MyWidget::startListen,recvWork,&RecvFile::startListen);
//更新进度条
connect(recvWork,&RecvFile::curPercent,ui->progressBar,&QProgressBar::setValue);
//启动线程
thread->start();
}
MyWidget::~MyWidget()
{
delete ui;
}
void MyWidget::on_buttonlisten_clicked()
{
//获取端口
unsigned short port = ui->lineEdport->text().toUShort();
//发出连接信号
emit startListen(port);
}
发送文件的.h文件
#ifndef SENDFILE_H
#define SENDFILE_H
#include
#include
#include
#include
class SendFile : public QObject
{
Q_OBJECT
public:
explicit SendFile(QObject *parent = nullptr);
//连接服务器
void connectServer(unsigned short port,QString ip);
//发送文件
void sendfile(QString path);
protected:
//发送文件数据
void sendfileData();
signals:
//通知主线程连接成功
void connectOK();
//通知主线程断开连接
void gameover();
//通知主线程发送文件进度百分比
void curPercent(int num);
public slots:
private:
QTcpSocket *tcpSocket;
QFile file;//文件对象
QString filename;//文件名字
qint16 filesize;//文件大小
qint16 sendfilesize;//已发送大小
QTimer *timer;
};
#endif // SENDFILE_H
发送文件的.cpp文件,利用一个QTimer对象做一个延时20ms,防止黏包问题,先创建一个头部信息,包含文件名和文件大小,让接收端先接收头部信息,20ms后再接收数据部分。
#include "sendfile.h"
#include
#include
#include
#include
#include
SendFile::SendFile(QObject *parent) : QObject(parent)
{
}
//连接服务器
void SendFile::connectServer(unsigned short port, QString ip)
{
//分配内存空间
tcpSocket = new QTcpSocket;
//连接服务器
tcpSocket->connectToHost(QHostAddress(ip),port);
//通知主线程连接成功
connect(tcpSocket,&QTcpSocket::connected,this,&SendFile::connectOK);
//通知主线程断开连接
connect(tcpSocket,&QTcpSocket::disconnected,this,[=]()
{
//断开连接,释放资源
tcpSocket->close();
tcpSocket->deleteLater();
emit gameover();
});
timer = new QTimer(this);
connect(timer,&QTimer::timeout,[=]()
{
//关闭定时器
timer->stop();
//发送文件数据
sendfileData();
});
}
//发送文件
void SendFile::sendfile(QString path)
{
file.setFileName(path);
//获取文件信息
QFileInfo info(path);
filesize = info.size();//获取文件大小
filename = info.fileName();//获取文件名
//只读方式打开文件
bool isOk = file.open(QIODevice::ReadOnly);
if(true == isOk)
{
//创建文件头信息
QString head = QString("%1##%2").arg(filename).arg(filesize);
//发送头部信息
qint64 len = tcpSocket->write(head.toUtf8());
if(len>0)
{
//防止tcp黏包问题,延时20ms
timer->start(20);
}
else
{
//发送失败
file.close();
}
}
else
{
return;
}
}
void SendFile::sendfileData()
{
qint64 len = 0;
//读多少发多少
do
{
//每次发送的大小
char buf[4*1024] = {0};
//记录每行数据
len = file.read(buf,sizeof(buf));
//计算百分比,发给主线程
sendfilesize += len;
int percent = (static_cast<int>(sendfilesize)*100)/filesize;
//发出更新进度条的信号
emit curPercent(percent);
//发送数据
tcpSocket->write(buf,len);
}while(len>0);
if(sendfilesize == filesize)
{
file.close();
filename.clear();
filesize = 0;
}
}
主窗口的.h文件
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include
namespace Ui {
class MyWidget;
}
class MyWidget : public QWidget
{
Q_OBJECT
public:
explicit MyWidget(QWidget *parent = 0);
~MyWidget();
signals:
//连接信号
void startConnect(unsigned short,QString);
//发送文件信号
void sendFile(QString path);
private slots:
void on_bConnect_clicked();
void on_bSelectFile_clicked();
void on_bSend_clicked();
private:
Ui::MyWidget *ui;
};
#endif // MYWIDGET_H
主窗口的.cpp文件
#include "mywidget.h"
#include "ui_mywidget.h"
#include
#include
#include
#include "sendfile.h"
MyWidget::MyWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::MyWidget)
{
ui->setupUi(this);
//设置ip和端口
ui->lineEdIP->setText("127.0.0.1");
ui->lineEdPort->setText("8888");
//设置进度条
ui->progressBar->setRange(0,100);
ui->progressBar->setValue(0);
//创建线程对象
QThread *thread = new QThread;
//创建任务对象
SendFile *sendWork = new SendFile;
//将任务对象移到子线程中
sendWork->moveToThread(thread);
//当发送sendFile信号时,让任务对象启动发文件处理函数
connect(this,&MyWidget::sendFile,sendWork,&SendFile::sendfile);
//通过信号让任务对象连接服务器
connect(this,&MyWidget::startConnect,sendWork,&SendFile::connectServer);
//处理连接成功的信号,弹出连接成功的提示
connect(sendWork,&SendFile::connectOK,this,[=]()
{
QMessageBox::information(this,"连接服务器","成功连接了服务器。");
});
//断开连接
connect(sendWork,&SendFile::gameover,this,[=]()
{
//释放资源
thread->quit();
thread->wait();
sendWork->deleteLater();
thread->deleteLater();
});
//更新进度条
connect(sendWork,&SendFile::curPercent,ui->progressBar,&QProgressBar::setValue);
//启动线程
thread->start();
}
MyWidget::~MyWidget()
{
delete ui;
}
void MyWidget::on_bConnect_clicked()
{
//获取ip和端口
QString ip = ui->lineEdIP->text();
unsigned short port = ui->lineEdPort->text().toUShort();
//发出连接信号
emit startConnect(port,ip);
}
void MyWidget::on_bSelectFile_clicked()
{
QString path = QFileDialog::getOpenFileName();
//判断路径是否为空
if(path.isEmpty())
{
QMessageBox::warning(this,"打开文件","选择路径不能为空");
return;
}
//将路径显示在文本栏中
ui->lineEdfilePath->setText(path);
}
void MyWidget::on_bSend_clicked()
{
//发送文件
emit sendFile(ui->lineEdfilePath->text());
}
这个简易的文件传输,发送端和接收端都是将数据处理部分放在了子线程,主线程只负责界面的更新和部分信号的发送,子线程处理完数据后发出信号告知主线程,让主线程做出相对应的处理,子线程通过继承QObject类的方式,利用信号与槽的方式进行启动子线程处理函数。
谢箭,何小群,LABVIEW实用程序设计,西南交通大学出版社,2017.07,第125页
胡璞编著,体育装备嵌入式技术,中国地质大学出版社,2014.09,第259页
开发借鉴 Qt实现基于多线程的文件传输(服务端,客户端)