QT - 創建TCP Socket通信


參考:https://blog.csdn.net/bailang_zhizun/article/details/78327974

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類的對象

  1.  
    QTcpServer* mp_TCPServer ;
  2.  
    mp_TCPServer = new QTcpServer();

2):監聽

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

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

其中,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函數向外部發送數據:

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

   b):客戶端通信機制

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

1):建立QTcpSocket類的對象

建立Socket的套接字:

  1.  
    QTcpSocket* mp_clientSocket;
  2.  
    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中添加具體如下代碼:

  1.  
    private:
  2.  
    Ui::SocketTCPServer *ui;
  3.  
     
  4.  
    QTcpServer *mp_TCPServer;
  5.  
    QTcpSocket *mp_TCPSocket;
  6.  
    private slots:
  7.  
     
  8.  
    void OnBtnInitSocket();
  9.  
    void OnBtnSendData();
  10.  
    void ServerReadData();
  11.  
    void ServerNewConnection();
  12.  
    void sServerDisConnection();

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

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

3):ServerNewConnection()具體實現:

  1.  
    //獲取客戶端連接
  2.  
    mp_TCPSocket = mp_TCPServer->nextPendingConnection();
  3.  
    if(!mp_TCPSocket)
  4.  
    {
  5.  
    QMessageBox::information( this, "QT網絡通信", "未正確獲取客戶端連接!");
  6.  
    return;
  7.  
    }
  8.  
    else
  9.  
    {
  10.  
    QMessageBox::information( this, "QT網絡通信", "成功接受客戶端的連接");
  11.  
    connect(mp_TCPSocket, SIGNAL(readyRead()), this, SLOT(ServerReadData()));
  12.  
    connect(mp_TCPSocket, SIGNAL(disconnected()), this, SLOT(sServerDisConnection()));
  13.  
    }

4):ServerReadData()具體實現:

  1.  
    char buffer[ 1024] = { 0};
  2.  
    mp_TCPSocket->read(buffer, 1024);
  3.  
    if( strlen(buffer) > 0)
  4.  
    {
  5.  
    QString showNsg = buffer;
  6.  
    ui->m_recvDataTextEdit->append(showNsg);
  7.  
    }
  8.  
    else
  9.  
    {
  10.  
    QMessageBox::information( this, "QT網絡通信", "未正確接收數據");
  11.  
    return;
  12.  
    }

5):OnBtnInitSocket()具體實現:

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

6):OnBtnSendData()具體實現:

  1.  
    char sendMsgChar[ 1024] = { 0};
  2.  
    QString sendMsg = ui->m_inputTextEdit->toPlainText();
  3.  
    if(sendMsg.isEmpty())
  4.  
    {
  5.  
    QMessageBox::information( this, "QT網絡通信", "發送數據為空,請輸入數據");
  6.  
    return;
  7.  
    }
  8.  
    strcpy_s(sendMsgChar, sendMsg.toStdString().c_str());
  9.  
    if(mp_TCPSocket->isValid())
  10.  
    {
  11.  
    int sendRe = mp_TCPSocket->write(sendMsgChar, strlen(sendMsgChar));
  12.  
    if( -1 == sendRe)
  13.  
    {
  14.  
    QMessageBox::information( this, "QT網絡通信", "服務端發送數據失敗!");
  15.  
    }
  16.  
    }
  17.  
    else
  18.  
    {
  19.  
    QMessageBox::information( this, "QT網絡通信", "套接字無效!");
  20.  
    }

7):sServerDisConnection()具體實現:

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

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

   b):客戶端示例

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

  1.  
    private slots:
  2.  
    void on_m_connectServerBtn_clicked();
  3.  
     
  4.  
    void on_pushButton_2_clicked();
  5.  
     
  6.  
    void ClientRecvData();
  7.  
     
  8.  
    private:
  9.  
    Ui::SocketTCPClient *ui;
  10.  
     
  11.  
    QTcpSocket *mp_clientSocket;

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

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

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

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

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

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

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

  1.  
    //將接收內容存儲到字符串中
  2.  
    char recvMsg[ 1024] = { 0};
  3.  
    int recvRe = mp_clientSocket->read(recvMsg, 1024);
  4.  
    if(recvRe == -1)
  5.  
    {
  6.  
    QMessageBox::information( this, "QT網絡通信", "接收服務端數據失敗!");
  7.  
    return;
  8.  
    }
  9.  
    QString showQstr = recvMsg;
  10.  
    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): 本人只是對最常見的實現方式進行了對比,屬於比較小白的。如有不足之處,還請見諒並指出,大家共同進步。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM