參考: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類的對象
-
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): 本人只是對最常見的實現方式進行了對比,屬於比較小白的。如有不足之處,還請見諒並指出,大家共同進步。