本文章原創(chuàng)于 www. 轉(zhuǎn)載請注明出處。 在上一節(jié)里我們使用TCP服務器發(fā)送一個字符串,,然后在TCP客戶端進行接收,。在這一節(jié)我們重新寫一個客戶端程序和一個服務器程序,這次我們讓客戶端進行文件的發(fā)送,,服務器進行文件的接收,。有了上一節(jié)的基礎,這一節(jié)的內(nèi)容就很好理解了,,注意一下幾個信號和槽的關(guān)聯(lián)即可,。當然,我們這次要更深入了解一下數(shù)據(jù)的發(fā)送和接收的處理方法,。
一,、客戶端 這次我們先講解客戶端,在客戶端里我們與服務器進行連接,,一旦連接成功,,就會發(fā)出connected()信號,這時我們就進行文件的發(fā)送,。 在上一節(jié)我們已經(jīng)看到,,發(fā)送數(shù)據(jù)時我們先發(fā)送了數(shù)據(jù)的大小信息。這一次,,我們要先發(fā)送文件的總大小,,然后文件名長度,然后是文件名,,這三部分我們合稱為文件頭結(jié)構(gòu),,最后再發(fā)送文件數(shù)據(jù)。所以在發(fā)送函數(shù)里我們就要進行相應的處理,,當然,,在服務器的接收函數(shù)里我們也要進行相應的處理。對于文件大小,,這次我們使用了qint64,,它是64位的,,可以表示一個很大的文件了。 1.同前一節(jié),,我們新建工程,,將工程命名為“tcpSender”。注意添加network模塊,。 2.我們在widget.ui文件中將界面設計如下。
這里“主機”后的Line Edit的objectName為hostLineEdit,;“端口”后的Line Edit的objectName為portLineEdit,;下面的Progress Bar的objectName為clientProgressBar,其value屬性設為0,;“狀態(tài)”Label的objetName為clientStatusLabel,;“打開”按鈕的objectName為openButton;“發(fā)送”按鈕的objectName為sendButton; 3.在widget.h 文件中進行更改,。 (1)添加頭文件#include <QtNetwork> (2)添加private變量: QTcpSocket *tcpClient; QFile *localFile; //要發(fā)送的文件 qint64 totalBytes; //數(shù)據(jù)總大小 qint64 bytesWritten; //已經(jīng)發(fā)送數(shù)據(jù)大小 qint64 bytesToWrite; //剩余數(shù)據(jù)大小 qint64 loadSize; //每次發(fā)送數(shù)據(jù)的大小 QString fileName; //保存文件路徑 QByteArray outBlock; //數(shù)據(jù)緩沖區(qū),,即存放每次要發(fā)送的數(shù)據(jù) (3)添加私有槽函數(shù): private slots: void send(); //連接服務器 void startTransfer(); //發(fā)送文件大小等信息 void updateClientProgress(qint64); //發(fā)送數(shù)據(jù),更新進度條 void displayError(QAbstractSocket::SocketError); //顯示錯誤 void openFile(); //打開文件 4.在widget.cpp文件中進行更改,。 添加頭文件:#include <QFileDialog> (1)在構(gòu)造函數(shù)中添加代碼: loadSize = 4*1024; totalBytes = 0; bytesWritten = 0; bytesToWrite = 0; tcpClient = new QTcpSocket(this); connect(tcpClient,SIGNAL(connected()),this,SLOT(startTransfer())); //當連接服務器成功時,,發(fā)出connected()信號,我們開始傳送文件 connect(tcpClient,SIGNAL(bytesWritten(qint64)),this, SLOT(updateClientProgress(qint64))); //當有數(shù)據(jù)發(fā)送成功時,,我們更新進度條 connect(tcpClient,SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(displayError(QAbstractSocket::SocketError))); ui->sendButton->setEnabled(false); //開始使”發(fā)送“按鈕不可用 我們主要是進行了變量的初始化和幾個信號和槽函數(shù)的關(guān)聯(lián),。 (2)實現(xiàn)打開文件函數(shù)。 void Widget::openFile() //打開文件 { fileName = QFileDialog::getOpenFileName(this); if(!fileName.isEmpty()) { ui->sendButton->setEnabled(true); ui->clientStatusLabel->setText(tr(“打開文件 %1 成功,!”) .arg(fileName)); } } 該函數(shù)將在下面的“打開”按鈕單擊事件槽函數(shù)中調(diào)用,。 (3)實現(xiàn)連接函數(shù)。 void Widget::send() //連接到服務器,,執(zhí)行發(fā)送 { ui->sendButton->setEnabled(false); bytesWritten = 0; //初始化已發(fā)送字節(jié)為0 ui->clientStatusLabel->setText(tr(“連接中…”)); tcpClient->connectToHost(ui->hostLineEdit->text(), ui->portLineEdit->text().toInt());//連接 } 該函數(shù)將在“發(fā)送”按鈕的單擊事件槽函數(shù)中調(diào)用,。 (4)實現(xiàn)文件頭結(jié)構(gòu)的發(fā)送。 void Widget::startTransfer() //實現(xiàn)文件大小等信息的發(fā)送 { localFile = new QFile(fileName); if(!localFile->open(QFile::ReadOnly)) { qDebug() << "open file error!"; return; } totalBytes = localFile->size(); //文件總大小 QDataStream sendOut(&outBlock,QIODevice::WriteOnly); sendOut.setVersion(QDataStream::Qt_4_6); QString currentFileName = fileName.right(fileName.size() - fileName.lastIndexOf('/')-1); sendOut << qint64(0) << qint64(0) << currentFileName; //依次寫入總大小信息空間,,文件名大小信息空間,,文件名 totalBytes += outBlock.size(); //這里的總大小是文件名大小等信息和實際文件大小的總和 sendOut.device()->seek(0); sendOut<<totalBytes<<qint64((outBlock.size() - sizeof(qint64)*2)); //返回outBolock的開始,用實際的大小信息代替兩個qint64(0)空間 bytesToWrite = totalBytes - tcpClient->write(outBlock); //發(fā)送完頭數(shù)據(jù)后剩余數(shù)據(jù)的大小 ui->clientStatusLabel->setText(tr("已連接")); outBlock.resize(0); } (5)下面是更新進度條,,也就是發(fā)送文件數(shù)據(jù),。 void Widget::updateClientProgress(qint64 numBytes) //更新進度條,實現(xiàn)文件的傳送 { bytesWritten += (int)numBytes; //已經(jīng)發(fā)送數(shù)據(jù)的大小 if(bytesToWrite > 0) //如果已經(jīng)發(fā)送了數(shù)據(jù) { outBlock = localFile->read(qMin(bytesToWrite,loadSize)); //每次發(fā)送loadSize大小的數(shù)據(jù),,這里設置為4KB,,如果剩余的數(shù)據(jù)不足4KB, //就發(fā)送剩余數(shù)據(jù)的大小 bytesToWrite -= (int)tcpClient->write(outBlock); //發(fā)送完一次數(shù)據(jù)后還剩余數(shù)據(jù)的大小 outBlock.resize(0); //清空發(fā)送緩沖區(qū) } else { localFile->close(); //如果沒有發(fā)送任何數(shù)據(jù),,則關(guān)閉文件 } ui->clientProgressBar->setMaximum(totalBytes); ui->clientProgressBar->setValue(bytesWritten); //更新進度條 if(bytesWritten == totalBytes) //發(fā)送完畢 { ui->clientStatusLabel->setText(tr(“傳送文件 %1 成功”).arg(fileName)); localFile->close(); tcpClient->close(); } } (6)實現(xiàn)錯誤處理函數(shù),。 void Widget::displayError(QAbstractSocket::SocketError) //顯示錯誤 { qDebug() << tcpClient->errorString(); tcpClient->close(); ui->clientProgressBar->reset(); ui->clientStatusLabel->setText(tr(“客戶端就緒”)); ui->sendButton->setEnabled(true); } (7)我們從widget.ui中分別進行“打開”按鈕和“發(fā)送”按鈕的單擊事件槽函數(shù),,然后更改如下。 void Widget::on_openButton_clicked() //打開按鈕 { openFile(); } void Widget::on_sendButton_clicked() //發(fā)送按鈕 { send(); } 5.我們?yōu)榱耸钩绦蛑械闹形牟伙@示亂碼,,在main.cpp文件中更改,。 添加頭文件:#include <QTextCodec> 在main函數(shù)中添加代碼:QTextCodec::setCodecForTr(QTextCodec::codecForLocale()); 6.運行程序,效果如下,。
7.程序整體思路分析,。 我們設計好界面,然后按下“打開”按鈕,,選擇我們要發(fā)送的文件,,這時調(diào)用了openFile()函數(shù)。然后我們點擊“發(fā)送”按鈕,,調(diào)用send()函數(shù),,與服務器進行連接。當連接成功時就會發(fā)出connected()信號,,這時就會執(zhí)行startTransfer()函數(shù),,進行文件頭結(jié)構(gòu)的發(fā)送,當發(fā)送成功時就會發(fā)出bytesWritten(qint64)信號,,這時我們執(zhí)行updateClientProgress(qint64 numBytes)進行文件數(shù)據(jù)的傳輸和進度條的更新,。這里使用了一個loadSize變量,我們在構(gòu)造函數(shù)中將其初始化為4*1024即4字節(jié),,它的作用是,,我們將整個大的文件分成很多小的部分進行發(fā)送,每部分為4字節(jié),。而當連接出現(xiàn)問題時就會發(fā)出error(QAbstractSocket::SocketError)信號,,這時就會執(zhí)行displayError()函數(shù)。對于程序中其他細節(jié)我們就不再分析,,希望大家能自己編程研究一下,。 二、服務器端,。 我們在服務器端進行數(shù)據(jù)的接收,。服務器端程序是很簡單的,我們開始進行監(jiān)聽,,一旦發(fā)現(xiàn)有連接請求就發(fā)出newConnection()信號,,然后我們便接受連接,開始接收數(shù)據(jù),。 1.新建工程,,名字為“tcpReceiver”。 2.我們更改widget.ui文件,,設計界面如下,。 其中“服務器端”Label的objectName為serverStatusLabel,;進度條Progress Bar的objectName為serverProgressBar,設置其value屬性為0,;“開始監(jiān)聽”按鈕的objectName為startButton,。 效果如下。
3.更改widget.h文件的內(nèi)容,。 (1)添加頭文件:#include <QtNetwork> (2)添加私有變量: QTcpServer tcpServer; QTcpSocket *tcpServerConnection; qint64 totalBytes; //存放總大小信息 qint64 bytesReceived; //已收到數(shù)據(jù)的大小 qint64 fileNameSize; //文件名的大小信息 QString fileName; //存放文件名 QFile *localFile; //本地文件 QByteArray inBlock; //數(shù)據(jù)緩沖區(qū) (3)添加私有槽函數(shù): private slots: void on_startButton_clicked(); void start(); //開始監(jiān)聽 void acceptConnection(); //建立連接 void updateServerProgress(); //更新進度條,,接收數(shù)據(jù) void displayError(QAbstractSocket::SocketError socketError); //顯示錯誤 4.更改widget.cpp文件。 (1)在構(gòu)造函數(shù)中添加代碼: totalBytes = 0; bytesReceived = 0; fileNameSize = 0; connect(&tcpServer,SIGNAL(newConnection()),this, SLOT(acceptConnection())); //當發(fā)現(xiàn)新連接時發(fā)出newConnection()信號 (2)實現(xiàn)start()函數(shù),。 void Widget::start() //開始監(jiān)聽 { ui->startButton->setEnabled(false); bytesReceived =0; if(!tcpServer.listen(QHostAddress::LocalHost,6666)) { qDebug() << tcpServer.errorString(); close(); return; } ui->serverStatusLabel->setText(tr(“監(jiān)聽”)); } (3)實現(xiàn)接受連接函數(shù),。 void Widget::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(); } (4)實現(xiàn)更新進度條函數(shù)。 void Widget::updateServerProgress() //更新進度條,,接收數(shù)據(jù) { QDataStream in(tcpServerConnection); in.setVersion(QDataStream::Qt_4_6); if(bytesReceived <= sizeof(qint64)*2) { //如果接收到的數(shù)據(jù)小于16個字節(jié),那么是剛開始接收數(shù)據(jù),,我們保存到//來的頭文件信息 if((tcpServerConnection->bytesAvailable() >= sizeof(qint64)*2) && (fileNameSize == 0)) { //接收數(shù)據(jù)總大小信息和文件名大小信息 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() << “open file error!”; return; } } else return; } if(bytesReceived < totalBytes) { //如果接收的數(shù)據(jù)小于總數(shù)據(jù),那么寫入文件 bytesReceived += tcpServerConnection->bytesAvailable(); inBlock = tcpServerConnection->readAll(); localFile->write(inBlock); inBlock.resize(0); } ui->serverProgressBar->setMaximum(totalBytes); ui->serverProgressBar->setValue(bytesReceived); //更新進度條 if(bytesReceived == totalBytes) { //接收數(shù)據(jù)完成時 tcpServerConnection->close(); localFile->close(); ui->startButton->setEnabled(true); ui->serverStatusLabel->setText(tr(“接收文件 %1 成功,!”) .arg(fileName)); } } (5)錯誤處理函數(shù),。 void Widget::displayError(QAbstractSocket::SocketError) //錯誤處理 { qDebug() << tcpServerConnection->errorString(); tcpServerConnection->close(); ui->serverProgressBar->reset(); ui->serverStatusLabel->setText(tr(“服務端就緒”)); ui->startButton->setEnabled(true); } (6)我們在widget.ui中進入“開始監(jiān)聽”按鈕的單擊事件槽函數(shù),更改如下,。 void Widget::on_startButton_clicked() //開始監(jiān)聽按鈕 { start(); } 5.我們?yōu)榱耸钩绦蛑械闹形牟伙@示亂碼,,在main.cpp文件中更改。 添加頭文件:#include <QTextCodec> 在main函數(shù)中添加代碼:QTextCodec::setCodecForTr(QTextCodec::codecForLocale()); 6.運行程序,,并同時運行tcpSender程序,,效果如下。 我們先在服務器端按下“開始監(jiān)聽”按鈕,,然后在客戶端輸入主機地址和端口號,,然后打開要發(fā)送的文件,點擊“發(fā)送”按鈕進行發(fā)送,。 在這兩節(jié)里我們介紹了TCP的應用,,可以看到服務器端和客戶度端都可以當做發(fā)送端或者接收端,而且數(shù)據(jù)的發(fā)送與接收只要使用相對應的協(xié)議即可,,它是可以根據(jù)用戶的需要來進行編程的,,沒有固定的格式。 |
|