[Qt教程] 第37篇 網絡(七)TCP(一)
樓主
發表於 2013-9-6 15:44:45 | 查看:
398| 回複: 1

TCP (一)
版權聲明
該文章原創於作者yafeilinux,轉載請注明出處!
導語
TCP即TransmissionControl
Protocol,傳輸控製協議。與UDP不同,它是麵向連接和數據流的可靠傳輸協議。也就是說,它能使一台計算機上的數據無差錯的發往網絡上的其他計算機,所以當要傳輸大量數據時,我們選用TCP協議。
TCP協議的程序使用的是客戶端/服務器(C/S)模式,在Qt中提供了QTcpSocket類來編寫客戶端程序,使用QTcpServer類編寫服務器端程序。我們在服務器端進行端口的**,一旦發現客戶端的連接請求,就會發出newConnection()信號,可以關聯這個信號到我們自己的槽進行數據的發送。而在客戶端,一旦有數據到來就會發出readyRead()信號,可以關聯此信號進行數據的接收。其實,在程序中最難理解的地方就是程序的發送和接收了,為了讓大家更好的理解,我們在這一節隻是講述一個傳輸簡單的字符串的例子,在下一節再進行擴展,實現任意文件的傳輸。
環境:Windows Xp + Qt 4.8.5+Qt Creator2.8.0
目錄
一、服務器端
二、客戶端
正文
一、服務器端
在服務器端的程序中,我們**本地主機的一個端口,這裏使用6666,然後關聯newConnection()信號與自己寫的sendMessage()槽。就是說一旦有客戶端的連接請求,就會執行sendMessage()函數,在這個函數裏我們發送一個簡單的字符串。
1.新建QtGui應用
項目名為tcpServer,基類選擇QWidget,類名為Widget。完成後打開項目文件tcpServer.pro並添加一行代碼:QT += network ,然後保存該文件。
2.在widget.ui的設計區添加一個Label,更改其顯示文本為“等待連接”,然後更改其objectName為statusLabel,用於顯示一些狀態信息。
3.在widget.h文件中做以下更改。
添加頭文件:#include <QtNetWork>
添加private對象:QTcpServer *tcpServer;
添加私有槽:
private slots:
void sendMessage();
4.在widget.cpp文件中進行更改。
在其構造函數中添加代碼:
tcpServer = new QTcpServer(this);
if(!tcpServer->listen(QHostAddress::LocalHost,6666))
{ //**本地主機的6666端口,如果出錯就輸出錯誤信息,並關閉
qDebug()
<< tcpServer->errorString();
close();
}
//連接信號和相應槽函數
connect(tcpServer,SIGNAL(newConnection()),this,SLOT(sendMessage()));
我們在構造函數中使用tcpServer的listen()函數進行**,然後關聯了newConnection()和我們自己的sendMessage()函數。
下麵我們實現sendMessage()函數。
void Widget::sendMessage()
{
//用於暫存我們要發送的數據
QByteArray block;
//使用數據流寫入數據
QDataStream out(&block,QIODevice::WriteOnly);
//設置數據流的版本,客戶端和服務器端使用的版本要相同
out.setVersion(QDataStream::Qt_4_6);
out<<(quint16) 0;
out<<tr("hello Tcp!!!");
out.device()->seek(0);
out<<(quint16) (block.size() - sizeof(quint16));
//我們獲取已經建立的連接的子套接字
QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
connect(clientConnection,SIGNAL(disconnected()),clientConnection,
SLOT(deleteLater()));
clientConnection->write(block);
clientConnection->disconnectFromHost();
//發送數據成功後,顯示提示
ui->statusLabel->setText("send message successful!!!");
}
這個是數據發送函數,我們主要介紹兩點:
(1)為了保證在客戶端能接收到完整的文件,我們都在數據流的最開始寫入完整文件的大小信息,這樣客戶端就可以根據大小信息來判斷是否接受到了完整的文件。而在服務器端,在發送數據時就要首先發送實際文件的大小信息,但是,文件的大小一開始是無法預知的,所以這裏先使用了out<<(quint16)
0;在block的開始添加了一個quint16大小的空間,也就是兩字節的空間,它用於後麵放置文件的大小信息。然後out<<tr("hello
Tcp!!!");輸入實際的文件,這裏是字符串。當文件輸入完成後我們再使用out.device()->seek(0);返回到block的開始,加入實際的文件大小信息,也就是後麵的代碼,它是實際文件的大小:out<<(quint16)
(block.size() - sizeof(quint16));
(2)在服務器端我們可以使用tcpServer的nextPendingConnection()函數來獲取已經建立的連接的Tcp套接字,使用它來完成數據的發送和其它操作。比如這裏,我們關聯了disconnected()信號和deleteLater()槽,然後我們發送數據
clientConnection->write(block);
然後是clientConnection->disconnectFromHost();
它表示當發送完成時就會斷開連接,這時就會發出disconnected()信號,而最後調用deleteLater()函數保證在關閉連接後刪除該套接字clientConnection。
5.這樣服務器的程序就完成了,可以先運行一下程序。
二、客戶端
我們在客戶端程序中向服務器發送連接請求,當連接成功時接收服務器發送的數據。
1.新建Qt Gui應用,
項目名tcpClient,基類選擇QWidget,類名為Widget。完成後打開項目文件tcpClient.pro並添加一行代碼:QT += network ,然後保存該文件。
2.我們在widget.ui中添加幾個標簽Label和兩個Line
Edit以及一個按鈕Push Button。設計效果如下圖所示。
![]()
其中“主機”後的LineEdit的objectName為hostLineEdit,“端口號”後的為portLineEdit。
“收到的信息”標簽的objectName為messageLabel 。
3.在widget.h文件中做更改。
添加頭文件:#include <QtNetwork>
添加private變量:
QTcpSocket *tcpSocket;
QString message; //存放從服務器接收到的字符串
quint16blockSize; //存放文件的大小信息
添加私有槽:
private slots:
void newConnect();
//連接服務器
void readMessage(); //接收數據
void displayError(QAbstractSocket::SocketError); //顯示錯誤
4.在widget.cpp文件中做更改。
(1)在構造函數中添加代碼:
tcpSocket = new QTcpSocket(this);
connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readMessage()));
connect(tcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),
this,SLOT(displayError(QAbstractSocket::SocketError)));
這裏關聯了tcpSocket的兩個信號,當有數據到來時發出readyRead()信號,我們執行讀取數據的readMessage()函數。當出現錯誤時發出error()信號,我們執行displayError()槽函數。
(2)實現newConnect()函數。
void Widget::newConnect()
{
blockSize
= 0; //初始化其為0
tcpSocket->abort(); //取消已有的連接
//連接到主機,這裏從界麵獲取主機地址和端口號
tcpSocket->connectToHost(ui->hostLineEdit->text(),
ui->portLineEdit->text().toInt());
}
這個函數實現了連接到服務器,下麵會在“連接”按鈕的單擊事件槽函數中調用這個函數。
(3)實現readMessage()函數。
void Widget::readMessage()
{
QDataStream in(tcpSocket);
in.setVersion(QDataStream::Qt_4_6);
//設置數據流版本,這裏要和服務器端相同
if(blockSize==0)
//如果是剛開始接收數據
{
//判斷接收的數據是否有兩字節,也就是文件的大小信息
//如果有則保存到blockSize變量中,沒有則返回,繼續接收數據
if(tcpSocket->bytesAvailable()
< (int)sizeof(quint16)) return;
in
>> blockSize;
}
if(tcpSocket->bytesAvailable()
< blockSize) return;
//如果沒有得到全部的數據,則返回,繼續接收數據
in
>> message;
//將接收到的數據存放到變量中
ui->messageLabel->setText(message);
//顯示接收到的數據
}
這個函數實現了數據的接收,它與服務器端的發送函數相對應。首先我們要獲取文件的大小信息,然後根據文件的大小來判斷是否接收到了完整的文件。
(4)實現displayError()函數。
void Widget::displayError(QAbstractSocket::SocketError)
{
qDebug()
<< tcpSocket->errorString(); //輸出錯誤信息
}
這裏簡單的實現了錯誤信息的輸出。
(5)我們在widget.ui中進入“連接”按鈕的單擊事件槽函數,然後更改如下。
void Widget::on_pushButton_clicked()
//連接按鈕
{
newConnect();
//請求連接
}
這裏直接調用了newConnect()函數。
5.我們運行程序,同時運行服務器程序,然後在“主機”後填入“localhost”,在“端口號”後填入“6666”,點擊“連接”按鈕,效果如下。
![]()
可以看到我們正確地接收到了數據。因為服務器端和客戶端是在同一台機子上運行的,所以我這裏填寫了“主機”為“localhost”,如果你在不同的機子上運行,需要在“主機”後填寫其正確的IP地址。
結語
到這裏我們最簡單的TCP應用程序就完成了,在下一節我們將會對它進行擴展,實現任意文件的傳輸。
涉及到的源碼: ![]() ![]() |
最後更新:2017-04-03 14:54:15