QT - 創建TCP Socket通信

QT創建TCP Socket通信

       最近在學習QT,瞭解到QT可以進行SOCKET網絡通信,進行學習,並建立一個簡單的聊天DEMO。爲了測試是否能與VS2012下的程序進行通信,在VS2012下建立一個客戶端程序,進行通信測試,發現可以進行通信。由此也可以證明,對於採用同一種通信協議(TCP)的兩個程序而言,不管是採用什麼編譯器,儘管採用的語法不同,仍是能夠進行通信的。下面先對QT的TCP通信機制進行簡單的介紹,然後再介紹基於QT的聊天DEMO具體的實現過程;最後介紹與VS2012下的程序通信。

1、QT的TCP Socket通信機制

        QT的TCP Socket通信仍然有服務端、客戶端之分。服務端通過監聽某個端口來監聽是否有客戶端連接到來,如果有連接到來,則建立新的SOCKET連接;客戶端通過IP和PORT連接服務端,當成功建立連接之後,就可進行數據的收發了。需要注意的是,在QT中,QT把SOCKET當成輸入輸出流來對待的,數據的收發是通過read()和write()來進行的,需要與我們常見的send()與recv()進行區分。

要在QT進行SOCKET通信,需要在 工程名.pro 文件中輸入 QT       += network,如下所示:

   a):服務端通信機制

        在服務端,建立SOCKET通信需要用到兩個類QTcpServer和QTcpSocket。其中QTcpServer是用來建立QT的Server端對象,QTcpSocket是用來建立SOCKET通信的Socket套接字對象。通信建立流程如下所示:

1):建立QTcpServer類的對象

QTcpServer* mp_TCPServer ;
mp_TCPServer = new QTcpServer();

2):監聽

      QT中,通過listen()建立對端口的監聽。使用方式如下:mp_TCPServer->listen(地址類型, 端口號);

int port = ui->m_portLineEdit->text().toInt();	//獲得端口號
if(!mp_TCPServer->listen(QHostAddress::Any, port))
{
     QMessageBox::information(this, "QT網絡通信", "服務器端監聽失敗!");
     return;
}

其中,QHostAddress定義了集中特殊的IP地址,如

QHostAddress::Null表示一個空地址;

QHostAddress::LocalHost表示IPv4的本機地址127.0.0.1;

QHostAddress::LocalHostIPv6表示IPv6的本機地址;

QHostAddress::Broadcast表示廣播地址255.255.255.255;

QHostAddress::Any表示IPv4的任意地址;

QHostAddress::AnyIPv6表示IPv6的任意地址。

3):關聯接收連接信號與槽函數

        服務端通過信號 SIGNAL:newConnection() 來判斷是否接收到了新的連接,當服務端接收到一個客戶端的連接時,就會觸發信號newConnection(),此時調用相應的槽函數(如自定義函數:ServerNewConnection())保存新接收到的連接;所以需要在服務端監聽端口之後建立信號與槽函數的連接。通過connect函數建立聯繫:

connect(mp_TCPServer, SIGNAL(newConnection()), this, SLOT(ServerNewConnection()));

        在ServerNewConnection()函數中,通過nextPendingConnection()函數獲得連接客戶端的SOCKET套接字:

mp_TCPSocket = mp_TCPServer->nextPendingConnection();

4):接收數據

        在QT中QT通過信號SIGNAL:readyRead()來判斷是否有數據傳入,當客戶端向服務端成功發送數據之後,就會在服務端觸發readyRead()信號,此時通過調用相應的自定義的槽函數(如:ServerReadData())保存接收到的數據;通過connect函數建立信號readyRead()與槽函數ServerReadData()的連接:

connect(mp_TCPSocket, SIGNAL(readyRead()), this, SLOT(ServerReadData()));

在接收函數ServerReadData()函數中通過read()函數獲取數據:

mp_TCPSocket->read(buffer, 1024);

需要注意的是read()函數有多個重載函數,保存接收數據的數據類型可以是QByteArray也可以是char*類型,根據個人習慣或者任務需求選擇合適的read()函數。不過,爲了保持一致性,建議選擇char*類型,一是因爲數據類型容易識別;二是因爲熟悉C\C++語言開發的對char*應該比較熟悉,防止使用上的錯誤。

5):發送數據

       在QT中,通過write函數向外部發送數據:

int sendRe = mp_TCPSocket->write(sendMsgChar, strlen(sendMsgChar));
if( -1 == sendRe)
{
   QMessageBox::information(this, "QT網絡通信", "服務端發送數據失敗!");
}

   b):客戶端通信機制

        客戶端的通信機制與服務端相比要相對簡單,只用到了QTcpSocket一個類。

1):建立QTcpSocket類的對象

建立Socket的套接字:

QTcpSocket* mp_clientSocket;
mp_clientSocket = new QTcpSocket();

2):連接服務端

客戶端通過connectToHost(IP, Port)函數連接服務端

mp_clientSocket->connectToHost(ip, port);

3):接收數據

       客戶端接收數據與服務端接收數據的機制是相同的。通過readyRead()信號是否被觸發來判斷是否有數據傳入,如果該信號被觸發,則調用自定義函數(如:ClientRecvData())來保存接收到的數據。通過connect()函數,將信號readyRead()與槽函數ClientRecvData()建立映射關係。

在槽函數ClientRecvData()中通過read()函數接收數據,具體使用方法請參考服務端接收數據

4):發送數據

客戶端發送數據也是通過write()函數來實現,具體使用方法請參考服務端發送數據

2、QT基於TCP Socket的通信實例

該部分主要是DEMO的具體實現。

   a):服務端示例

1):在sockettcpserver.h中添加具體如下代碼:

private:
    Ui::SocketTCPServer *ui;

    QTcpServer *mp_TCPServer;
    QTcpSocket *mp_TCPSocket;
private slots:

    void OnBtnInitSocket();
    void OnBtnSendData();
    void ServerReadData();
    void ServerNewConnection();
    void sServerDisConnection();

2):在構造函數中添加如下代碼:

ui->m_portLineEdit->setText("5550");
    connect(ui->m_initSocketBtn, SIGNAL(clicked()), this, SLOT(OnBtnInitSocket()));
    connect(ui->m_sendData, SIGNAL(clicked()), this, SLOT(OnBtnSendData()));

3):ServerNewConnection()具體實現:

//獲取客戶端連接
    mp_TCPSocket = mp_TCPServer->nextPendingConnection();
    if(!mp_TCPSocket)
    {
        QMessageBox::information(this, "QT網絡通信", "未正確獲取客戶端連接!");
        return;
    }
    else
    {
        QMessageBox::information(this, "QT網絡通信", "成功接受客戶端的連接");
        connect(mp_TCPSocket, SIGNAL(readyRead()), this, SLOT(ServerReadData()));
        connect(mp_TCPSocket, SIGNAL(disconnected()), this, SLOT(sServerDisConnection()));
    }

4):ServerReadData()具體實現:

char buffer[1024] = {0};
    mp_TCPSocket->read(buffer, 1024);
    if( strlen(buffer) > 0)
    {
        QString showNsg = buffer;
        ui->m_recvDataTextEdit->append(showNsg);
    }
    else
    {
        QMessageBox::information(this, "QT網絡通信", "未正確接收數據");
        return;
    }

5):OnBtnInitSocket()具體實現:

mp_TCPServer = new QTcpServer();
    int port = ui->m_portLineEdit->text().toInt();
    if(!mp_TCPServer->listen(QHostAddress::Any, port))
    {
        QMessageBox::information(this, "QT網絡通信", "服務器端監聽失敗!");
        return;
    }
    else
    {
        QMessageBox::information(this, "QT網絡通信", "服務器監聽成功!");
    }
    connect(mp_TCPServer, SIGNAL(newConnection()), this, SLOT(ServerNewConnection()));

6):OnBtnSendData()具體實現:

char sendMsgChar[1024] = {0};
    QString sendMsg = ui->m_inputTextEdit->toPlainText();
    if(sendMsg.isEmpty())
    {
        QMessageBox::information(this, "QT網絡通信", "發送數據爲空,請輸入數據");
        return;
    }
    strcpy_s(sendMsgChar, sendMsg.toStdString().c_str());
    if(mp_TCPSocket->isValid())
    {
        int sendRe = mp_TCPSocket->write(sendMsgChar, strlen(sendMsgChar));
        if( -1 == sendRe)
        {
            QMessageBox::information(this, "QT網絡通信", "服務端發送數據失敗!");
        }
    }
    else
    {
        QMessageBox::information(this, "QT網絡通信", "套接字無效!");
    }

7):sServerDisConnection()具體實現:

QMessageBox::information(this, "QT網絡通信", "與客戶端的連接斷開");
    return;

8):ui界面設計具體如下:

   b):客戶端示例

1):在sockettcpclient.h中添加如下代碼:

private slots:
    void on_m_connectServerBtn_clicked();

    void on_pushButton_2_clicked();

    void ClientRecvData();

private:
    Ui::SocketTCPClient *ui;

    QTcpSocket *mp_clientSocket;

2):在構造函數中添加如下代碼:

ui->m_serverIPLineEdit->setText("127.0.0.1");
    ui->m_serverPortLineEdit_2->setText("5550");

3):on_m_connectServerBtn_clicked()函數具體實現如下:

mp_clientSocket = new QTcpSocket();
    QString ip = ui->m_serverIPLineEdit->text();\
    int port = ui->m_serverPortLineEdit_2->text().toInt();
    mp_clientSocket->connectToHost(ip, port);
    if(!mp_clientSocket->waitForConnected(30000))
    {
        QMessageBox::information(this, "QT網絡通信", "連接服務端失敗!");
        return;
    }
     connect(mp_clientSocket, SIGNAL(readyRead()), this, SLOT(ClientRecvData()));

4):on_pushButton_2_clicked()函數具體實現如下:

 //獲取TextEdit控件中的內容
    QString sendMsg = ui->m_sendTextEdit->toPlainText();
    char sendMsgChar[1024] = {0};
    strcpy_s(sendMsgChar, sendMsg.toStdString().c_str());
    int sendRe = mp_clientSocket->write(sendMsgChar, strlen(sendMsgChar));
    if(sendRe == -1)
    {
         QMessageBox::information(this, "QT網絡通信", "向服務端發送數據失敗!");
         return;
    }

5):ClientRecvData()函數具體實現如下:

//將接收內容存儲到字符串中
    char recvMsg[1024] = {0};
    int recvRe = mp_clientSocket->read(recvMsg, 1024);
    if(recvRe == -1)
    {
        QMessageBox::information(this, "QT網絡通信", "接收服務端數據失敗!");
        return;
    }
    QString showQstr = recvMsg;
    ui->m_recvTextEdit_2->setText(showQstr);

6):客戶端ui具體設計如下:

7):最終實現如下圖所示:

需要具體工程文件的可以訪問:http://download.csdn.net/download/bailang_zhizun/10037740

3、基於QT的SOCKET程序與基於VS2012的SOCKET程序的通信

爲了驗證QT中SOCKET程序能否與VS2012中的SOCKET程序正常通信,編寫了一個VS2012版的客戶端程序,與QT版的服務端程序進行通信。雙方都採用正常方式編寫。經測試,雙方能夠正常通信。

結果如下所示:

PS:

1): 通過對兩各程序的對比,總的來說,QT版的實現方式要比VS2012版的實現方式簡單很多,因爲QT把SOCKET相關的類進行了很好的封裝,只暴露了幾個簡單的接口函數就能夠實現SOCET的通信;而VS2012版的類的封裝性不如QT,使用起來比較麻煩,需要記比較多的接口函數。

而且在QT中,基本上不需要翻看其他的內容,如果要查看某個函數的用法只需要按F1就可以,很方便;而VS2012版的,你懂得;

2): 其中我覺得區別最大的就是SOCKET通信的接收連接、接收數據的機制。在QT中,它採用的是信號-槽的形式,關於SOCKET通信的相關操作,可以通過信號的方式來觸發對應的函數;而在VS2012中,它的實現方式則就要傳統很多。比如拿服務端接收連接來說,QT只需要連接信號newConnection()與接收函數即可,不管會接收多少個連接,都會以非阻塞的方式在對應的槽函數中建立對應的套接字;而在VS2012中,接收一個連接很簡單,但是當要接收多個連接時,while循環顯然是不可用的,只能建立線程函數專門接收連接。

對於接收數據也是一樣的,QT只需要建立信號readyRead()與具體的槽函數的映射關係,然後在槽函數讀取數據即可。而在VS2012中,則需要通過while循環去接收數據,對於需要並行處理數據的程序來說,則需要引入多線程。

3): 本人只是對最常見的實現方式進行了對比,屬於比較小白的。如有不足之處,還請見諒並指出,大家共同進步。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章