文件傳輸協議介紹
文件傳輸協議是包含於應用層中,在服務器端與客戶端通信過程中需要兩條鏈路的支持:一個是能夠在服務器端和客戶端之間傳遞數據文件的命令請求的鏈路,另一個是能夠實現服務器端和客戶端之間數據文件的處理的鏈路。 FTP使用TCP進行高速運輸服務,具有可靠性,同時能夠減小或消除在不同系統環境下傳輸文件的不確定性和不兼容性,從而體現它的時效性。文件傳輸協議依賴的是服務器進程和客戶端進程,其中服務器進程能夠實現爲兩個或兩個以上的客戶端進程提供數據傳輸請求服務。在文件傳輸過程中,服務器端的組成分爲兩類:一個是主要的進程,功能是接收來自客戶端新的請求並對該請求進行處理和響應;另一個是次要的進程,主要負責處理客戶端靠後的文件數據請求。客戶端的組成也分爲兩部分:一個是主要的進程,負責發送和服務器連接的數據請求;另一個是次要的進程,負責等待與服務器連接之後的單個靠後的文件數據請求。
文件傳輸工作流程
文件傳輸需要服務器端和客戶端之間的相互配合才能實現發送。在服務器端定義一個定時器實現延時發送,客戶端發送文件數據傳輸的請求,服務器端處理並響應文件數據請求,接着回饋給客戶端,接收確認信息,實現與FTP服務器程序相連,並將請求結果發送到客戶端。例如,開發用戶者利用客戶端發出請求命令,要求服務器向開發用戶傳輸一個視頻文件,服務器會接受請求並響應這條請求命令,讀取視頻文件中的內容信息,然後將該文視頻件發送到客戶端上並設置進度條觀察文件傳輸的進度,最後將傳輸的文件存放在開發用戶者創建的目錄中。
服務器端發送文件
(1)先在serverfile.h裏面定義監聽套接字和通信套接字以及文件對象、大小、名字,最重要的就是定義一個定時器;
QTcpServer *tcpServer; //監聽套接字
QTcpSocket *tcpSocket; //通信套接字
QFile file; //文件對象
QString fileName; //文件名字
qint64 fileSize; //文件大小
qint64 sendSize; //已經發送文件的大小
QTimer timer; //定時器
(2)在serverfile.cpp中利用監聽套接字進行監聽並分配對象,利用listen函數識別端口號8888;通過通信套接字獲取對方IP地址和端口號;
tcpServer = new QTcpServer(this);//監聽套接字
tcpServer->listen(QHostAddress::Any, 8888);
//獲取對方的ip和端口
QString ip = tcpSocket->peerAddress().toString();
quint16 port = tcpSocket->peerPort();
(3)在UI界面選擇按鈕,在選擇的按鈕中建立槽函數。通過“選擇文本”的按鈕建立的槽函數選擇對應路徑下的視頻文件,如果選擇的文件路徑有效則進行獲取視頻文件的名字和大小;通過“發送文件”的按鈕建立的槽函數將選擇的視頻文件發送併發送文件的大小;
QString filePath = QFileDialog::getOpenFileName(this, "open", "../");
if(false == filePath.isEmpty()) //如果選擇文件路徑有效
{
fileName.clear();
fileSize = 0;
//獲取文件信息
QFileInfo info(filePath);
fileName = info.fileName(); //獲取文件名字
fileSize = info.size(); //獲取文件大小
sendSize = 0; //發送文件的大小
}
(4)在通信套接字中進行write寫入時需要將其編碼爲Utf-8,否則中文會亂碼,再通過調用定時器延時20ms後啓動定時器開始發送;
qint64 len = tcpSocket->write( head.toUtf8() );
if(len > 0)
{
//定時器延時 20 ms
timer.start(20);
}
(5)定義一個buf數組設置每次發送數據的的大小,調用read函數實現往文件中讀數據,在利用write函數將讀取的數據進行發送,讀多少,發多少;
//每次發送數據的大小
char buf[4*1024] = {0};
len = 0;
//往文件中讀數據
len = file.read(buf, sizeof(buf));
//發送數據,讀多少,發多少
len = tcpSocket->write(buf, len);
(6)通過if循環語句判斷文件是否發送完畢,如果發送完畢後就打印“文件發送完畢”的信息,並利用disconnectFromHost函數和close函數主動斷開與客戶端之間的連接。
if(QString(buf) == "file done")
{//文件接收完畢
ui->textEdit->append("文件發送完畢");
file.close();
//斷開客戶端端口
tcpSocket->disconnectFromHost();
tcpSocket->close();
}
服務器在傳輸文件的過程中需要注意的首先在槽函數中要定義文件頭,防止TCP黏包,同時起到加密作用,還有就是必須使用定時器,不然文件傳輸不能成功。
客戶端接收文件
(1)和服務器端一樣首先在clientfile.h裏面定義通信套接字和文件對象、大小、名字。定義方法和服務器中定義的一樣;
(2)同樣需要獲取IP和端口號,在lineEdit文本框內輸入對應網絡下的IP和定義的端口號,利用connectToHost函數主動和服務器連接;
(3)使用connect函數進行連接,並提示是否連接成功,在textEdit部件中打印“準備就緒,等待服務器傳送文件……”的信息;
connect(tcpSocket, &QTcpSocket::connected,
[=]()
{
//提示連接成功
ui->textEdit->clear();
ui->textEdit->append("準備就緒,等待服務器傳送文件……");
});
(4)在客戶端進行判斷是否接收來自服務器發送的文件,如果是的話就將視頻文件的文件名和內存大小顯示出來並把接收的信息返回給服務器;
(5)設置進度條,觀察文件傳輸情況,如果傳輸完成,進度條會加載到100%;
isStart = true;
//設置進度條
ui->progressBar->setValue(0);
(6)通過循環判斷文件是否傳輸完成,如果傳輸完成首先在Qt運行結果上打印“file done”,同時給服務器端發送接收完成的信息並彈出一個可視化小窗口提示“文件接收完成”的信息,最後關閉文件並利用disconnectFromHost函數和close函數主動斷開連接。
if(recvSize == fileSize)
{
tcpSocket->write("file done");
ui->progressBar->setValue(fileSize/1024); //最大值
QMessageBox::information(this, "完成", "文件接收完成");
file.close(); //關閉文件
ui->progressBar->setValue(0); //當前值
//斷開連接
tcpSocket->disconnectFromHost();
tcpSocket->close();
}
在客戶端接收文件過程中需要注意的是必須通過使用標誌位isStart進行循環判斷是否能夠接收,如果可以能夠進行接收,那麼標誌位isStart就設置爲false,防止第二次重複的接收,否則就通過使用標誌位isOK進行循環判斷並調用disconnectFromHost函數和close函數進行連接的斷開和客戶端的關閉。
Qt實現服務器端與客戶端窗口
界面上包含五部分,兩個文本框,三個按鈕。Read文本框是將客戶端中寫入的信息讀取並顯示在文本框中,Write文本框是在服務器端進行數據寫入,方便客戶端進行數據讀取。三個按鈕爲發送、傳輸文件和斷開連接。發送按鈕實現的是將寫入的信息發送至客戶端;傳輸文件的按鈕實現的是進入傳送文件的界面,在這個界面中實現的是視頻文件的傳輸;斷開連接的按鈕實現的是與客戶端連接的斷開。
服務器端的可視化窗口
客戶端的可視化窗口
文字數據傳輸測試
輸入的IP號爲“192.168.1.108”,爲了區別於視頻文件數據的傳輸這裏的端口號設置爲“9999”,點擊“start connect”按鈕,在服務器端和客戶端的Read文本框內就會顯示“Succefful Connection with Server”的信息。
文件數據傳輸測試
在服務器端文件傳輸窗口上只有一個文本框用來顯示視頻文件路徑和傳輸結果,在客戶端文件傳輸窗口上有輸入IP和端口號的文本框,一個“Ready to receive”的按鈕,一個進行顯示接收的文件名字和大小的文本框,還有一個進度條進行文件傳輸進度的顯示。
主要代碼
clientfile.cpp
#include "clientfile.h"
#include "ui_clientfile.h"
#include <QDebug>
#include <QMessageBox>
#include <QHostAddress>
ClientFile::ClientFile(QWidget *parent) :
QWidget(parent),
ui(new Ui::ClientFile)
{
ui->setupUi(this);
tcpSocket = new QTcpSocket(this);
isStart = true;
ui->progressBar->setValue(0); //當前值
setWindowTitle("客戶端");
connect(tcpSocket, &QTcpSocket::connected,
[=]()
{
//提示連接成功
ui->textEdit->clear();
ui->textEdit->append("準備就緒,等待服務器傳送文件……");
}
);
connect(tcpSocket, &QTcpSocket::readyRead,
[=]()
{
//取出接收的內容
QByteArray buf = tcpSocket->readAll();
if(true == isStart)
{
isStart = false;
fileName = QString(buf).section("##",0,0);
fileSize = QString(buf).section("##",1,1).toInt();
recvSize = 0;
file.setFileName(fileName);
bool isOk = file.open(QIODevice::WriteOnly);
if(false == isOk)
{
qDebug()<<"WriteOnly error 49";
tcpSocket->disconnectFromHost();
tcpSocket->close();
return;
}
QString str = QString("接收的文件: [%1: %2kb]").arg(fileName).arg(fileSize/1024);
//QMessageBox::information(this, "文件信息", str);
ui->textEdit->setText(str);
ui->progressBar->setMinimum(0); //最小值
ui->progressBar->setMaximum(fileSize/1024); //最大值
ui->progressBar->setValue(0); //當前值
}
else {
qDebug()<<"123456";
qint64 len = file.write(buf);
if(len > 0)
{
recvSize += len; //累計接收大小
qDebug() << len;
}
ui->progressBar->setValue(recvSize/1024);
if(recvSize == fileSize)
{
//先給服務發送(接收文件完成的信息)
tcpSocket->write("file done");
ui->progressBar->setValue(fileSize/1024); //最大值
QMessageBox::information(this, "完成", "文件接收完成");
file.close(); //關閉文件
ui->progressBar->setValue(0); //當前值
//斷開連接
tcpSocket->disconnectFromHost();
tcpSocket->close();
}
}
}
);
}
ClientFile::~ClientFile()
{
delete ui;
}
void ClientFile::on_ButtonConnect_clicked()
{
//獲取服務器的ip和端口
QString ip = ui->lineEditIP->text();
quint16 port = ui->lineEditPort->text().toInt();
//主動和服務器連接
tcpSocket->connectToHost(QHostAddress(ip), port);
isStart = true;
//設置進度條
ui->progressBar->setValue(0);
}
void ClientFile::on_pushButton_2_clicked()
{
emit mySignal();
}
severfile.cpp
#ifndef CLIENTFILE_H
#define CLIENTFILE_H
#include <QWidget>
#include <QTcpSocket>
#include <QFile>
namespace Ui {
class ClientFile;
}
class ClientFile : public QWidget
{
Q_OBJECT
public:
explicit ClientFile(QWidget *parent = 0);
~ClientFile();
private slots:
void on_ButtonConnect_clicked();
void on_pushButton_2_clicked();
signals:
void mySignal();
private:
Ui::ClientFile *ui;
QTcpSocket *tcpSocket;
QFile file; //文件對象
QString fileName; //文件名字
qint64 fileSize; //文件大小
qint64 recvSize; //已經接收文件的大小
bool isStart; //標誌位,是否爲頭部信息
};
#endif // CLIENTFILE_H
感謝大家關注點贊!!!!!