QT TCP socket通訊(一)
TCP即Transmission Control Protocol,傳輸控制協議。與UDP不同,它是面向連線和資料流的可靠傳輸協議。也就是說,它能使一臺計算機上的資料無差錯的發往網路上的其他計算機,所以當要傳輸大量資料時,我們選用TCP協議。
TCP協議的程式使用的是客戶端/伺服器模式,在Qt中提供了QTcpSocket類來編寫客戶端程式,使用QTcpServer類編寫伺服器端程式。我們在伺服器端進行埠的監聽,一旦發現客戶端的連線請求,就會發出newConnection()訊號,我們可以關聯這個訊號到我們自己的槽函式,進行資料的傳送。而在客戶端,一旦有資料到來就會發出readyRead()訊號,我們可以關聯此訊號,進行資料的接收。其實,在程式中最難理解的地方就是程式的傳送和接收了,為了讓大家更好的理解,我們在這一節只是講述一個傳輸簡單的字串的例子,在下一節再進行擴充套件,實現任意檔案的傳輸。
一、伺服器端。
在伺服器端的程式中,我們監聽本地主機的一個埠,這裡使用6666,然後我們關聯newConnection()訊號與自己寫的sendMessage()槽函式。就是說一旦有客戶端的連線請求,就會執行sendMessage()函式,在這個函式裡我們傳送一個簡單的字串。
1.我們新建Qt4 Gui Application,工程名為“tcpServer”,選中QtNetwork模組,Base class選擇QWidget。(說明:如果一些Qt Creator版本沒有新增模組一項,我們就需要在工程檔案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. .我們新建Qt4 Gui Application,工程名為“tcpClient”,選中QtNetwork模組,Base class選擇QWidget。
2,我們在widget.ui中新增幾個標籤Label和兩個Line Edit以及一個按鈕Push Button。
其中“主機”後的Line Edit的objectName為hostLineEdit,“埠號”後的為portLineEdit。
“收到的資訊”標籤的objectName為messageLabel 。
3.在widget.h檔案中做更改。
新增標頭檔案:#include <QtNetwork>
新增private變數:
QTcpSocket *tcpSocket;
QString message; //存放從伺服器接收到的字串
quint16 blockSize; //存放檔案的大小資訊
新增私有槽函式:
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地址。