Qt多线程文件传输项目
通信流程
项目流程
项目文件
pro文件添加network模块
服务端
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "mytcpsever.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_start_clicked();
void on_selectfile_clicked();
void on_pushButton_clicked();
signals:
void start(QString name); //通知子线程可以工作啦
private:
Ui::MainWindow *ui;
MyTcpSever* m_server;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include <QMessageBox>
#include <QFileDialog>
#include <QRandomGenerator>
#include "sendfile.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle("文件发送端");
ui->ip->setText("127.0.0.1");
qDebug()<<"当前主线程的Id:" <<QThread::currentThreadId();
m_server = new MyTcpSever(this);
ui->port->setText("8989");
connect(m_server,&MyTcpSever::newClient,this,[=](qintptr socket) //子线程worker对象使用
{
//在ui中显示已连接
ui->status->setText("已连接");
//处理子线程闲逛的动作
QThread* subThread = new QThread;
//添加工作的类,使其工作移动到子线程中去工作
//将socket文件内容传递进去
sendfile* worker = new sendfile(socket); //不能指定this父对象,work要移动到子线程中,若添加this则不可指定
worker->moveToThread(subThread);
connect(this,&MainWindow::start,worker,&sendfile::working); //主线程调用start信号,子线程就可以调用working工作
//当子线程working工作完成之后发出一个信号,主线程得到信号之后就可以销毁这些资源
connect(worker,&sendfile::done,this,[=]()
{
//销毁了子线程,不能继续发送文件啦
// qDebug()<<"销毁子线程和任务对象资源....";
// subThread->quit(); //有些任务还没有完成
// subThread->wait(); //等待完成
// subThread->deleteLater(); //销毁子线程
// worker->deleteLater(); //也销毁worker进程
// ui->status->setText("未连接");
});
connect(ui->close,&QPushButton::clicked,this,[=]()
{
//销毁了子线程,不能继续发送文件啦
qDebug()<<"销毁子线程和任务对象资源....";
subThread->quit(); //有些任务还没有完成
subThread->wait(); //等待完成
subThread->deleteLater(); //销毁子线程
worker->deleteLater(); //也销毁worker进程
ui->status->setText("未连接");
ui->start->setEnabled(true);
});
connect(worker,&sendfile::text,this,[=](QByteArray msg)
{
//修改每次发送的不同颜色
QVector<QColor> colors =
{
Qt::red,Qt::green,Qt::black,Qt::blue,Qt::darkRed,Qt::cyan,Qt::magenta
};
int index = QRandomGenerator::global()->bounded(colors.size()); //获取colors里面的随机数
//取出某一种颜色
ui->msg->setTextColor(colors.at(index)); //设置一种随机颜色
ui->msg->append(msg);
});
//同步文件发送的总长度
connect(worker,&sendfile::tot_size_signal,this,[=](qint64 size)
{
//qDebug()<<"发送文件的总大小为"<<size;
ui->progressBar->setMinimum(0);
ui->progressBar->setMaximum(static_cast<int>(size));
ui->progressBar->setValue(0); //设置当前值
});
//同步文件发送的目前长度
connect(worker,&sendfile::now_size_signal,this,[=](qint64 size)
{
//qDebug()<<"发送当前文件大小"<<size;
ui->progressBar->setValue(static_cast<int>(size)); //设置当前值
});
subThread->start(); //启动子线程 ,启动之后,woker不能工作,需要主线程给子线程发送信号,让其子线程工作
});
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_start_clicked()
{
//绑定端口
unsigned short port = ui->port->text().toUShort();
m_server->listen(QHostAddress(ui->ip->text()),port);
//通信的套接字对象不可以跨线程访问
ui->status->setText("已启动服务器");
ui->start->setEnabled(false);
}
void MainWindow::on_selectfile_clicked()
{
QString path = QFileDialog::getOpenFileName(this);//获取文件
if(!path.isEmpty())
{
//将得到路径设置到
ui->path->setText(path);
}
qDebug()<<"用户选中的内容为空";
}
//发送文件
void MainWindow::on_pushButton_clicked() //发送文件按钮点击之后
{
//得到文件路径
if(ui->path->text().isEmpty())
{
QMessageBox::information(this,"提示","要发送的文件不能为空");
return;
}
if(!(ui->status->text() == "已连接"))
{
QMessageBox::information(this,"提示","还未连接终端或服务器");
return;
}
emit start(ui->path->text());
}
mytcpserver.h
#ifndef MYTCPSEVER_H
#define MYTCPSEVER_H
#include <QObject>
#include <QTcpServer>
class MyTcpSever : public QTcpServer
{
Q_OBJECT
public:
explicit MyTcpSever(QObject *parent = nullptr);
protected:
//虚函数,改写行为
void incomingConnection(qintptr socketDescriptor);
signals:
void newClient(qintptr socket);
public slots:
};
#endif // MYTCPSEVER_H
mytcpserver.cpp
#include "mytcpsever.h"
MyTcpSever::MyTcpSever(QObject *parent) : QTcpServer(parent)
{
}
void MyTcpSever::incomingConnection(qintptr socketDescriptor)
{
//将文件描述符发送出去
emit newClient(socketDescriptor);
}
sendfile.h
#ifndef SENDFILE_H
#define SENDFILE_H
#include <QObject>
#include <QTcpSocket>
class sendfile : public QObject
{
Q_OBJECT //Qt的信号槽机制
public:
explicit sendfile(qintptr socket,QObject *parent = nullptr);
void working(QString name);
signals:
void done(); //working 完成之后
void text(QByteArray msg); //发送的文件内容
void tot_size_signal(qint64 size); //得到文件总长度,发送信号文件总长度
void now_size_signal(qint64 size); //每次发送,同步
public slots:
private:
qintptr m_socket;
QTcpSocket* m_tcp;
qint64 totalsize; //记录文件的总长度
qint64 nowsize; //记录文件的现有长度
};
#endif // SENDFILE_H
sendfile.cpp
#include "sendfile.h"
#include <QThread>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QtEndian>
sendfile::sendfile(qintptr socket,QObject *parent) : QObject(parent)
{
m_socket = socket; //传递的用于通信的套接字,通过此可以与主函数建立连接的套接字通信
}
void sendfile::working(QString path)
{
qDebug()<<"当前子线程的Id:" <<QThread::currentThreadId();
m_tcp = new QTcpSocket;
m_tcp->setSocketDescriptor(m_socket); //设置socket文件描述符,m_socket就可以通信啦
connect(m_tcp,&QTcpSocket::disconnected,this,[=]()
{
m_tcp->close();
m_tcp->deleteLater();
emit done();
qDebug() << "客户端数据已经接受完毕,并断开了连接,开始销毁套接字对象,拜拜...";
});
qDebug()<<"发送的文件名字:"<<path;
nowsize = 0;
//通过QFile打开文件
QFile file(path);
//获取文件的总长度
QFileInfo FileData(path);
totalsize = FileData.size();
emit tot_size_signal(totalsize);
//将文件总大小传过去
QString size = QString::number(totalsize);
qint64 length = m_tcp->write(size.toUtf8());
m_tcp->waitForBytesWritten();
if(length > 0)
{
QThread::msleep(50); //休息
}
bool bl = file.open(QFile::ReadOnly);
if(bl)
{
while(!file.atEnd()) //如果文件没有读完
{
QByteArray line = file.readLine();
// 添加包头
int len = qToBigEndian(line.size());
QByteArray data(reinterpret_cast<char*>(&len), 4);
data.append(line);
// 发送数据
m_tcp->write(data);
//读取数据
// 信号
emit text(line);
//发送目前的line量
nowsize +=line.size();
emit now_size_signal(nowsize);
//qDebug()<<"发送的数据为"<<line;
//
QThread::msleep(50); //休息
}
}
file.close();
}
客户端
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "recvfile.h"
#include <QThread>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_connect_clicked();
signals:
void startConnect(QString ip,unsigned short port);
private:
Ui::MainWindow *ui;
QThread* subThread;
RecvFile* worker;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include <QMessageBox>
#include <QRandomGenerator>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle("接收端");
//主线程负责窗口时间,子线程负责文件传输
qDebug()<<"当前主线程线程ID:"<<QThread::currentThreadId();
//设置默认值
ui->ip->setText("127.0.0.1");
ui->port->setText("8989");
//创建子线程
subThread = new QThread;
worker = new RecvFile; //不可指定父对象
//worker想工作,除了subThread start还需要信号量连接调用workin
worker->moveToThread(subThread); //移动到子线程中区
subThread->start(); //子线程开始工作
connect(this,&MainWindow::startConnect,worker,&RecvFile::connectServer);
//当子线程连接服务器成功,通知主线程
connect(worker,&RecvFile::connectOK,this,[=]()
{
//通知程序的使用者
QMessageBox::information(this,"提示","已经成功连接到了服务器");
ui->connect->setText("已连接");
ui->connect->setEnabled(false);
});
//当子线程接收到数据呈现给父进程,将接受的数据呈现到UI上面
connect(worker,&RecvFile::message,this,[=](QByteArray msg)
{
//修改每次发送的不同颜色
QVector<QColor> colors =
{
Qt::red,Qt::green,Qt::black,Qt::blue,Qt::darkRed,Qt::cyan,Qt::magenta
};
int index = QRandomGenerator::global()->bounded(colors.size()); //获取colors里面的随机数
//取出某一种颜色
ui->msg->setTextColor(colors.at(index)); //设置一种随机颜色
ui->msg->append(msg);
});
//当子进程结束
connect(worker,&RecvFile::gameover,this,[=]()
{
//子线程运行结束,进行资源的释放
qDebug()<<"子进程文件上传完毕";
});
connect(ui->close,&QPushButton::clicked,this,[=]()
{
subThread->quit();
subThread->wait();
subThread->deleteLater();
ui->connect->setEnabled(true);
ui->connect->setText("连接服务器");
});
//当子进程更新总文件大小,设置progressBar
connect(worker,&RecvFile::total_size_signal,this,[=](qint64 size)
{
qDebug()<<"接受文件总大小为"<<size;
ui->progressBar->setMinimum(0);
ui->progressBar->setMaximum(static_cast<int>(size));
ui->progressBar->setValue(0); //设置当前
});
connect(worker,&RecvFile::now_size_signal,this,[=](qint64 size)
{
//qDebug()<<"接受文件目前大小为"<<size;
ui->progressBar->setValue(static_cast<int>(size)); //设置当前
});
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_connect_clicked()
{
//拿到IP端口,将Ip和端口传递给子线程,在子线程new QTcp套接字对象,在子线程中接受数据
QString ip =ui->ip->text(); //读取ip
unsigned short port = ui->port->text().toUShort();
//通过信号传递给子线程
emit startConnect(ip,port);
}
recvfile.h
#ifndef RECVFILE_H
#define RECVFILE_H
#include <QObject>
#include <QTcpSocket>
class RecvFile : public QObject
{
Q_OBJECT
public:
explicit RecvFile(QObject *parent = nullptr);
~RecvFile();
//连接服务器的函数
void connectServer(QString ip,unsigned short port);
void dealData();
signals:
void connectOK();
void message(QByteArray msg);
void gameover();
void total_size_signal(qint64);
void now_size_signal(qint64);
public slots:
private:
QTcpSocket* m_tcp;
bool first_total = true; //首次读取文件总大小
qint64 nowsize =0;
};
#endif // RECVFILE_H
recvfile.cpp
#include "recvfile.h"
#include <QHostAddress>
#include <QDebug>
#include <QThread>
#include <QtEndian>
#include <QDataStream>
RecvFile::RecvFile(QObject *parent) : QObject(parent)
{
}
RecvFile::~RecvFile()
{
//销毁套接字
m_tcp->close();
m_tcp->deleteLater();
}
void RecvFile::connectServer(QString ip, unsigned short port)
{
qDebug()<<"当子线程线程ID:"<<QThread::currentThreadId();
// 连接服务器
m_tcp = new QTcpSocket;
//连接服务器
m_tcp->connectToHost(QHostAddress(ip),port);
connect(m_tcp,&QTcpSocket::connected,this,&RecvFile::connectOK); //成功建立连接,发送连接成功对象
//接受到数据, 服务器发送给客户端数据完整之后
connect(m_tcp,&QTcpSocket::readyRead,this,[=]()
{
//读取文件总大小
if(first_total)
{
first_total = false;
QByteArray array = m_tcp->readLine();
qDebug()<<"array传入文件大小为:"<<array.data();
emit total_size_signal(array.toInt());
}
//QByteArray all = m_tcp->readAll();
// emit message(all);
//对数据进行拆包
else {
dealData();
emit gameover();
}
});
}
void RecvFile::dealData()
{
int totalBytes = 0;
int recvBytes = 0;
QByteArray block; //存储对应的数据块
//判断有没有数据
if(m_tcp->bytesAvailable() == 0)
{
qDebug()<<"没有数据咯";
return ;
}
//读包头 可读的字节大于4个字节 //包头只可以读取字节,无法获取总字节
if(static_cast<int>(m_tcp->bytesAvailable()) >= static_cast<int>(sizeof (int)))
{
QByteArray head = m_tcp->read(sizeof(int));
totalBytes = qFromBigEndian(*reinterpret_cast<int*>(head.data()));
//qDebug() << "接收方包头的长度: " << totalBytes;
}
else {
return; //递归 也会结束
}
//根据包头去读 数据块
while(totalBytes-recvBytes >0 &&m_tcp->bytesAvailable() > 0)
{
block.append( m_tcp->read(totalBytes-recvBytes));
recvBytes = block.size();
nowsize +=recvBytes;
emit now_size_signal(nowsize);
}
if(totalBytes == recvBytes)
{
emit message(block);
}
//如果还有数据
if(m_tcp->bytesAvailable() > 0)
{
qDebug()<<"开始 递归调用....";
dealData();
}
}
运行结果
运行结果