學習筆記:QT網路程式設計:C2S基於TCP的檔案傳輸
阿新 • • 發佈:2018-12-31
預處理: 1在.pro加入一條語句 QT += network 記得儲存檔案 2.標頭檔案中可包含標頭檔案/儘量用前向宣告(因為只宣告不用) TCP檔案傳輸
傳送時是先寫入固定的緩衝區,再用資料包作為載體傳送; 而讀取時接收的是資料包,要先對接收到的資料進行判斷,檔案頭提取出來建立本地檔案,檔案內容才存入緩衝區內寫入建立的檔案。
每次傳輸的資料大小是由傳送端決定的
1.訊號SIGNAL也是函式!
客戶端: (1)類的處理 私有成員: QTcpSocket * tcpClient ; //不像伺服器類,並沒有特定的CLIENT類
伺服器端:單執行緒接收來自客戶端的檔案資料 (1)類的處理 私有成員: QTcpServer tcpServer ;//專用的伺服器類 QTcpSocket * tcpServerConnection ;// 一次只支援一個連線 (多個連線涉及多執行緒) qint64 totalBytes ; // 存放總大小資訊 qint64 bytesReceived ; // 已收到資料的大小 qint64 fileNameSize ; // 檔名的大小資訊 QString fileName ; // 存放檔名 QFile * localFile ; // 本地檔案 QByteArray inBlock ; // 資料緩衝區 ui介面: “監聽”按鈕 進度條 (2) 建構函式: 繫結“新連線請求訊號”,“連線處理槽函式” (3)事件迴圈 1.按下監聽按鈕 開始監聽“連線請求訊號”、初始化各種資料以及reset進度條
totalBytes = 0;//總的待接收檔案位元組數 bytesReceived = 0; //已經接收的位元組數 fileNameSize = 0; //檔名大小 用於判斷當前接收資料包是檔案頭還是檔案內容 2. 新連線請求訊號: 一次檔案傳輸 一個訊號 1.繫結“準備好開始讀取”和“讀取過程處理”、“連接出錯”的訊號和槽
connect( tcpServerConnection, SIGNAL(readyRead()),// this, SLOT(updateServerProgress())); 對比客戶端的: connect( tcpClient, SIGNAL( bytesWritten( qint64)), // this, SLOT(updateClientProgress( qint64)));
2.關閉伺服器 不再進行監聽 3.準備好開始讀取: 一次檔案傳輸 多個訊號 1.宣告一個臨時資料包來接收收到的資料包,繫結其和私有成員中的套接字,並設定版本號 若不設定,則無法正常讀取 2.拆包處理:按資料包傳送格式一個個拆取 若收到的資料是檔案頭,則需要用 臨時的資料包接收本次資料, 提取檔名併為其建立檔案,記錄待接收檔案總的大小 若已經開始接受檔案資料,則將 資料存入緩衝區, 追加到已建立的檔案末尾,已接收的資料與待接收總大小比較,判斷是否已經完成傳輸 3.重新整理記錄檔案傳輸過程的變數 4.重新整理進度條進度 若還在傳輸: ui-> serverProgressBar-> setMaximum( totalBytes); ui-> serverProgressBar-> setValue( bytesReceived); 若傳輸完成: ui-> serverStatusLabel-> setText( tr( "接收檔案 %1 成功!") . arg( fileName)); // %1 用來為檔名佔位 4. 連接出錯:
程式碼實現: 伺服器端
#ifndef SERVER_H
#define SERVER_H
#include <QDialog>
#include <QAbstractSocket>
#include <QTcpServer>
class QTcpSocket;
class QFile;
namespace Ui {
class Server;
}
class Server : public QDialog
{
Q_OBJECT
public:
explicit Server(QWidget *parent = 0);
~Server();
private:
Ui::Server *ui;
QTcpServer tcpServer;
QTcpSocket *tcpServerConnection;
qint64 totalBytes; // 存放總大小資訊
qint64 bytesReceived; // 已收到資料的大小
qint64 fileNameSize; // 檔名的大小資訊
QString fileName; // 存放檔名
QFile *localFile; // 本地檔案
QByteArray inBlock; // 資料緩衝區
private slots:
void start();
void acceptConnection();
void updateServerProgress();
void displayError(QAbstractSocket::SocketError socketError);
//
void on_startButton_clicked();
};
#endif // SERVER_H
#include "server.h"#include "ui_server.h"#include <QtNetwork>Server::Server(QWidget *parent) : QDialog(parent), ui(new Ui::Server){ ui->setupUi(this); connect(&tcpServer, SIGNAL(newConnection()), this, SLOT(acceptConnection()));}Server::~Server(){ delete ui;}void Server::start(){ if (!tcpServer.listen(QHostAddress::LocalHost, 6666)) { qDebug() << tcpServer.errorString(); close(); return; }
//判斷是否正常開啟監聽 ui->startButton->setEnabled(false); totalBytes = 0; bytesReceived = 0; fileNameSize = 0; ui->serverStatusLabel->setText(tr("監聽")); ui->serverProgressBar->reset();}void Server::acceptConnection(){ tcpServerConnection = tcpServer.nextPendingConnection(); connect(tcpServerConnection, SIGNAL(readyRead()), this, SLOT(updateServerProgress())); connect(tcpServerConnection, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(displayError(QAbstractSocket::SocketError))); ui->serverStatusLabel->setText(tr("接受連線")); // 關閉伺服器,不再進行監聽 tcpServer.close();}void Server::updateServerProgress(){ QDataStream in(tcpServerConnection); in.setVersion(QDataStream::Qt_4_0); // 如果接收到的資料小於16個位元組,儲存到來的檔案頭結構 if (bytesReceived <= sizeof(qint64)*2) { if((tcpServerConnection->bytesAvailable() >= sizeof(qint64)*2) && (fileNameSize == 0)) { // 接收資料總大小資訊和檔名大小資訊 in >> totalBytes >> fileNameSize; bytesReceived += sizeof(qint64) * 2; } if((tcpServerConnection->bytesAvailable() >= fileNameSize)// && (fileNameSize != 0)) { // 接收檔名,並建立檔案 in >> fileName; ui->serverStatusLabel->setText(tr("接收檔案 %1 …") .arg(fileName)); bytesReceived += fileNameSize; localFile = new QFile(fileName); if (!localFile->open(QFile::WriteOnly)) { qDebug() << "server: open file error!"; return; } } else { return; } } // 如果接收的資料小於總資料,那麼寫入檔案 if (bytesReceived < totalBytes) { bytesReceived += tcpServerConnection->bytesAvailable(); inBlock = tcpServerConnection->readAll(); localFile->write(inBlock); inBlock.resize(0); } ui->serverProgressBar->setMaximum(totalBytes); ui->serverProgressBar->setValue(bytesReceived); // 接收資料完成時 if (bytesReceived == totalBytes) { tcpServerConnection->close(); localFile->close(); ui->startButton->setEnabled(true); ui->serverStatusLabel->setText(tr("接收檔案 %1 成功!") .arg(fileName)); }}void Server::displayError(QAbstractSocket::SocketError socketError){ qDebug() << tcpServerConnection->errorString(); tcpServerConnection->close(); ui->serverProgressBar->reset(); ui->serverStatusLabel->setText(tr("服務端就緒")); ui->startButton->setEnabled(true);}// 開始監聽按鈕void Server::on_startButton_clicked(){ start();}
客戶端接收一次性資料的大概流程:
類的處理:
1.私有成員加入一個用於接收的套接字(生命週期最好和主介面相同)
2 ui介面建立兩個input label用於接收使用者輸入想連線host的ip和埠號
建構函式:
1.繫結“傳送按鈕點選訊號”和“傳送的資料槽函式”
事件迴圈:
點擊發送按鈕:
1.abort之前的連線並請求一個新連線
2.繫結 “連線成功訊號”以及槽函式
連線成功:
1.輸出資訊到控制檯,表明連線成功觸發了這個函式
#ifndef CLIENT_H
#define CLIENT_H
#include <QDialog>
#include <QAbstractSocket>
class QTcpSocket;
class QFile;
namespace Ui {
class Client;
}
class Client : public QDialog
{
Q_OBJECT
public:
explicit Client(QWidget *parent = 0);
~Client();
private:
Ui::Client *ui;
QTcpSocket *tcpClient;
QFile *localFile; // 要傳送的檔案
qint64 totalBytes; // 傳送資料的總大小
qint64 bytesWritten; // 已經發送資料大小
qint64 bytesToWrite; // 剩餘資料大小
qint64 payloadSize; // 每次傳送資料的大小
QString fileName; // 儲存檔案路徑
QByteArray outBlock; // 資料緩衝區,即存放每次要傳送的資料塊
private slots:
void openFile();
void send();
void startTransfer();
void updateClientProgress(qint64);
void displayError(QAbstractSocket::SocketError);
void on_openButton_clicked();
void on_sendButton_clicked();
};
#endif // CLIENT_H
#include "client.h"
#include "ui_client.h"
#include <QtNetwork>
#include <QFileDialog>
Client::Client(QWidget *parent) :
QDialog(parent),
ui(new Ui::Client)
{
ui->setupUi(this);
payloadSize = 64*1024; // 64KB
totalBytes = 0;
bytesWritten = 0;
bytesToWrite = 0;
tcpClient = new QTcpSocket(this);
// 當連線伺服器成功時,發出connected()訊號,開始傳送檔案
connect(tcpClient, SIGNAL(connected()), this, SLOT(startTransfer()));//
connect(tcpClient, SIGNAL(bytesWritten(qint64)), //
this, SLOT(updateClientProgress(qint64)));
connect(tcpClient, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(displayError(QAbstractSocket::SocketError)));
ui->sendButton->setEnabled(false);
}
Client::~Client()
{
delete ui;
}
void Client::openFile()
{
fileName = QFileDialog::getOpenFileName(this);
if (!fileName.isEmpty()) {
ui->sendButton->setEnabled(true);
ui->clientStatusLabel->setText(tr("開啟檔案 %1 成功!").arg(fileName));
}
}
void Client::send()
{
ui->sendButton->setEnabled(false);
// 初始化已傳送位元組為0
bytesWritten = 0;
ui->clientStatusLabel->setText(tr("連線中…"));
tcpClient->connectToHost(ui->hostLineEdit->text(),
ui->portLineEdit->text().toInt());
}
void Client::startTransfer()
{
localFile = new QFile(fileName);
if (!localFile->open(QFile::ReadOnly)) {
qDebug() << "client: open file error!";
return;
}
// 獲取檔案大小
totalBytes = localFile->size();
QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
sendOut.setVersion(QDataStream::Qt_4_0);
QString currentFileName = fileName.right(fileName.size()
- fileName.lastIndexOf('/')-1);//
// 保留總大小資訊空間、檔名大小資訊空間,然後輸入檔名
sendOut << qint64(0) << qint64(0) << currentFileName;
// 這裡的總大小是總大小資訊、檔名大小資訊、檔名和實際檔案大小的總和
totalBytes += outBlock.size(); //
sendOut.device()->seek(0);
// 返回outBolock的開始,用實際的大小資訊代替兩個qint64(0)空間
sendOut << totalBytes << qint64((outBlock.size() - sizeof(qint64)*2));
// 傳送完檔案頭結構後剩餘資料的大小
bytesToWrite = totalBytes - tcpClient->write(outBlock);
ui->clientStatusLabel->setText(tr("已連線"));
outBlock.resize(0);
}
void Client::updateClientProgress(qint64 numBytes)
{
// 已經發送資料的大小
bytesWritten += (int)numBytes;
// 如果已經發送了資料
if (bytesToWrite > 0) {
// 每次傳送payloadSize大小的資料,這裡設定為64KB,如果剩餘的資料不足64KB,
// 就傳送剩餘資料的大小
outBlock = localFile->read(qMin(bytesToWrite, payloadSize));
// 傳送完一次資料後還剩餘資料的大小
bytesToWrite -= (int)tcpClient->write(outBlock);
// 清空傳送緩衝區
outBlock.resize(0);
} else { // 如果沒有傳送任何資料,則關閉檔案
localFile->close();
}
// 更新進度條
ui->clientProgressBar->setMaximum(totalBytes);
ui->clientProgressBar->setValue(bytesWritten);
// 如果傳送完畢
if(bytesWritten == totalBytes) {
ui->clientStatusLabel->setText(tr("傳送檔案 %1 成功").arg(fileName));
localFile->close();
tcpClient->close();
}
}
void Client::displayError(QAbstractSocket::SocketError)
{
qDebug() << tcpClient->errorString();
tcpClient->close();
ui->clientProgressBar->reset();
ui->clientStatusLabel->setText(tr("客戶端就緒"));
ui->sendButton->setEnabled(true);
}
// 開啟按鈕
void Client::on_openButton_clicked()
{
ui->clientProgressBar->reset();
ui->clientStatusLabel->setText(tr("狀態:等待開啟檔案!"));
openFile();
}
// 傳送按鈕
void Client::on_sendButton_clicked()
{
send();
}