1. 效果
先看看效果圖
- 這是傳輸檔案完成的介面
- 客戶端
- 服務端
2. 知識準備
其實檔案傳輸和聊天室十分相似,只不過一個傳輸的是文字,一個傳輸的是檔案,而這方面的知識,我已經在前面的部落格寫過了,不瞭解的同學可以去看一下
還有多執行緒相關的知識
2.1 關於多執行緒
這次是用多執行緒實現的檔案傳輸系統,其中對客戶端來說,子執行緒負責連線伺服器,傳送檔案,主執行緒負責修改進度條,對服務端來說,也是用子執行緒來處理客戶端的請求
2.2 關於檔案傳輸
檔案傳輸採用的是,對客戶端,首先是傳送出整個檔案的大小,需要用到QFileInfo這個類,然後再傳送檔案
對服務端,先接收檔案的大小,然後判斷,當接收的檔案大小等於第一次接收的檔案大小時,停止接收,斷開連線
3. 原始碼
我在程式碼裡面都有非常詳細的註釋,所以就直接放上程式碼啦
3.1 客戶端
標頭檔案 mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
void startConnect(unsigned short,QString);
// 傳送檔案訊號
void sendFile(QString path);
private slots:
void on_connectServer_clicked();
void on_selFile_clicked();
void on_sendFile_clicked();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
原始檔 mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QThread>
#include "sendfile.h"
#include <QFileDialog>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 設定IP和埠
ui->ip->setText("127.0.0.1");
ui->port->setText("8989");
// 設定進度條
ui->progressBar->setRange(0,100);
ui->progressBar->setValue(0);
// 客戶端在子執行緒中連線伺服器
// 建立執行緒物件
QThread* t = new QThread;
// 建立任務物件
SendFile* worker = new SendFile;
// 將worker移動到子執行緒t中
worker->moveToThread(t);
// 當傳送sendFile訊號,讓worker的sendFile函式處理(子執行緒)
connect(this,&MainWindow::sendFile,worker,&SendFile::sendFile);
// 通過訊號,讓worker開始工作
// 因為worker 已經移動到了子執行緒中,因此connectServer這個槽函式是在子執行緒中執行的
connect(this,&MainWindow::startConnect,worker,&SendFile::connectServer);
// 處理子執行緒傳送的訊號
// 連線成功
connect(worker,&SendFile::connectOK,this,[=](){
QMessageBox::information(this,"連線伺服器","已經成功的連線了伺服器,恭喜!");
});
// 斷開連線
connect(worker,&SendFile::gameover,this,[=](){
// 資源釋放
t->quit();
t->wait();
worker->deleteLater();
t->deleteLater();
});
connect(worker,&SendFile::curPercent,ui->progressBar,&QProgressBar::setValue);
// 啟動執行緒
t->start();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_connectServer_clicked()
{
QString ip = ui->ip->text();
unsigned short port = ui->port->text().toUShort();
emit startConnect(port,ip);
}
void MainWindow::on_selFile_clicked()
{
QString path = QFileDialog::getSaveFileName();
// 判斷路徑是否為空
if(path.isEmpty())
{
QMessageBox::warning(this,"開啟檔案","選擇的檔案路徑不能為空");
return;
}
ui->filePath->setText(path);
}
void MainWindow::on_sendFile_clicked()
{
// 傳送檔案訊號
emit sendFile(ui->filePath->text());
}
標頭檔案 Send File.h
#ifndef SENDFILE_H
#define SENDFILE_H
#include <QObject>
#include <QTcpSocket>
class SendFile : public QObject
{
Q_OBJECT
public:
explicit SendFile(QObject *parent = nullptr);
// 連線伺服器
void connectServer(unsigned short port,QString ip);
// 傳送檔案
void sendFile(QString path);
signals:
// 通知主執行緒連線成功
void connectOK();
// 通知主執行緒連線成功
void gameover();
// 通知主執行緒傳送檔案進度百分比
void curPercent(int num);
private:
QTcpSocket* m_tcp;
};
#endif // SENDFILE_H
原始檔SendFile.cpp
#include "sendfile.h"
#include <QFile>
#include <QHostAddress>
#include <QFileInfo>
SendFile::SendFile(QObject* parent) : QObject(parent)
{
}
void SendFile::connectServer(unsigned short port, QString ip)
{
m_tcp = new QTcpSocket;
m_tcp->connectToHost(QHostAddress(ip),port);
// 通知主執行緒連線成功
connect(m_tcp,&QTcpSocket::connected,this,&SendFile::connectOK);
// 通知主執行緒斷開連線
connect(m_tcp,&QTcpSocket::disconnected,this,[=](){
// 斷開連線,釋放資源
m_tcp->close();
m_tcp->deleteLater();
emit gameover();
});
}
void SendFile::sendFile(QString path)
{
QFile file(path);
// 獲取檔案資訊
QFileInfo info(path);
int fileSize = info.size();
file.open(QFile::ReadOnly);
// 一行一行的讀檔案
while(!file.atEnd()){
static int num = 0;
// 為了讓伺服器端知道什麼時候停止接收,所以得傳送檔案的大小
if(num ==0){
m_tcp->write((char*)&fileSize,4);
}
QByteArray line = file.readLine();
// 計算百分比,發給主執行緒
num +=line.size();
int percent =(num*100/fileSize);
emit curPercent(percent);
m_tcp->write(line);
}
}
3.2 服務端
標頭檔案mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTcpServer>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_setListen_clicked();
private:
Ui::MainWindow *ui;
QTcpServer* m_s;
};
#endif // MAINWINDOW_H
原始檔maindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QTcpSocket>
#include "recvfile.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_s = new QTcpServer(this);
connect(m_s,&QTcpServer::newConnection,this,[=](){
QTcpSocket* tcp = m_s->nextPendingConnection();
// 建立子執行緒,tcp通過引數傳遞
RecvFile* subThread = new RecvFile(tcp);
subThread->start();
connect(subThread,&RecvFile::over,this,[=](){
subThread->exit();
subThread->wait();
subThread->deleteLater();
QMessageBox::information(this,"檔案接受","檔案接收完畢!!!");
});
});
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_setListen_clicked()
{
unsigned short port = ui->port->text().toUShort();
m_s->listen(QHostAddress::Any,port);
}
標頭檔案recvfile.h
#ifndef RECVFILE_H
#define RECVFILE_H
#include <QThread>
#include <QTcpSocket>
class RecvFile : public QThread
{
Q_OBJECT
public:
explicit RecvFile(QTcpSocket* tcp,QObject *parent = nullptr);
protected:
void run() override;
private:
QTcpSocket* m_tcp;
signals:
void over();
};
#endif // RECVFILE_H
原始檔recvfile.cpp
#include "recvfile.h"
#include <QFile>
RecvFile::RecvFile(QTcpSocket* tcp,QObject *parent) : QThread(parent)
{
m_tcp = tcp;
}
void RecvFile::run()
{
QFile* file = new QFile("recv.txt");
file->open(QFile::WriteOnly);
// 接收資料
connect(m_tcp,&QTcpSocket::readyRead,this,[=](){
static int count = 0;
static int total = 0;
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();
file->deleteLater();
emit over();
}
});
// 進入事件迴圈
exec();
}
3.4 檔案目錄
4. 結束語
如果有些小夥伴需要工程檔案等,可以聯絡我