1. 程式人生 > >QT TCP socket通訊(一)

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,用於顯示一些狀態資訊。如下:

 Hosted by ImageHost.org

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.這樣伺服器的程式就完成了,我們先執行一下程式。

 Hosted by ImageHost.org

二、客戶端。

我們在客戶端程式中向伺服器傳送連線請求,當連線成功時接收伺服器傳送的資料。

1. .我們新建Qt4 Gui Application,工程名為“tcpClient”,選中QtNetwork模組,Base class選擇QWidget。

2,我們在widget.ui中新增幾個標籤Label和兩個Line Edit以及一個按鈕Push Button。

 Hosted by ImageHost.org

其中“主機”後的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”,點選“連線”按鈕,效果如下。

 Hosted by ImageHost.org

可以看到我們正確地接收到了資料。因為伺服器端和客戶端是在同一臺機子上執行的,所以我這裡填寫了“主機”為“localhost”,如果你在不同的機子上執行,需要在“主機”後填寫其正確的IP地址。