QT TCP网络编程
先看下效果圖:
一:客戶端編程
QT提供了QTcpSocket類(lèi),可以直接實(shí)例化一個(gè)客戶端,可在help中索引如下:
The QTcpSocket class provides a TCP socket. More... Header #include <QTcpSocket> qmake QT += network Inherits: QAbstractSocket Inherited By: QSslSocket 從這里,我們可以看到,必須要在.pro文件中添加QT += network才可以進(jìn)行網(wǎng)絡(luò)編程,否則是訪問(wèn)不到<QTcpSocket>頭文件的。客戶端讀寫(xiě)相對(duì)簡(jiǎn)單,我們看一下代碼頭文件: #ifndef MYTCPCLIENT_H #define MYTCPCLIENT_H#include <QMainWindow> #include <QTcpSocket> #include <QHostAddress> #include <QMessageBox> namespace Ui { class MyTcpClient; }class MyTcpClient : public QMainWindow {Q_OBJECTpublic:explicit MyTcpClient(QWidget *parent = 0);~MyTcpClient();private:Ui::MyTcpClient *ui;QTcpSocket *tcpClient;private slots://客戶端槽函數(shù)void ReadData();void ReadError(QAbstractSocket::SocketError);void on_btnConnect_clicked();void on_btnSend_clicked();void on_pushButton_clicked(); };#endif // MYTCPCLIENT_H 我們?cè)诖翱陬?lèi)中,定義了一個(gè)私有成員QTcpSoket *tcpClient。1) 初始化QTcpSocket
在構(gòu)造函數(shù)中,我們需要先對(duì)其進(jìn)行實(shí)例化,并連接信號(hào)與槽函數(shù):
2)建立連接 和 斷開(kāi)連接
tcpClient->connectToHost(ui->edtIP->text(), ui->edtPort->text().toInt());if (tcpClient->waitForConnected(1000)) // 連接成功則進(jìn)入if{}{ui->btnConnect->setText("斷開(kāi)");ui->btnSend->setEnabled(true);} a)建立TCP連接的函數(shù):void connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode = ReadWrite)是從QAbstractSocket繼承下來(lái)的public function,同時(shí)它又是一個(gè)virtual function。作用為:Attempts to make a connection to address on port port。 b)等待TCP連接成功的函數(shù):bool waitForConnected(int msecs = 30000)同樣是從QAbstractSocket繼承下來(lái)的public function,同時(shí)它又是一個(gè)virtual function。作用為:Waits until the socket is connected, up to msecs milliseconds. If the connection has been established, this function returns true; otherwise it returns false. In the case where it returns false, you can call error() to determine the cause of the error.上述代碼中,edtIP, edtPort是ui上的兩個(gè)lineEditor,用來(lái)填寫(xiě)服務(wù)器IP和端口號(hào)。btnConnect是“連接/斷開(kāi)”復(fù)用按鈕,btnSend是向服務(wù)器發(fā)送數(shù)據(jù)的按鈕,只有連接建立之后,才將其setEnabled。 tcpClient->disconnectFromHost();if (tcpClient->state() == QAbstractSocket::UnconnectedState \|| tcpClient->waitForDisconnected(1000)) //已斷開(kāi)連接則進(jìn)入if{}{ui->btnConnect->setText("連接");ui->btnSend->setEnabled(false);} a)斷開(kāi)TCP連接的函數(shù):void disconnectFromHost()是從QAbstractSocket繼承的public function,同時(shí)它又是一個(gè)virtual function。作用為:Attempts to close the socket. If there is pending data waiting to be written, QAbstractSocket will enter ClosingState and wait until all data has been written. Eventually, it will enter UnconnectedState and emit the disconnected() signal. b)等待TCP斷開(kāi)連接函數(shù):bool waitForDisconnected(int msecs = 30000),同樣是從QAbstractSocket繼承下來(lái)的public function,同時(shí)它又是一個(gè)virtual function。作用為:Waits until the socket has disconnected, up to msecs milliseconds. If the connection has been disconnected, this function returns true; otherwise it returns false. In the case where it returns false, you can call error() to determine the cause of the error.3)讀取服務(wù)器發(fā)送過(guò)來(lái)的數(shù)據(jù)
readyRead()是QTcpSocket從父類(lèi)QIODevice中繼承下來(lái)的信號(hào):This signal is emitted once every time new data is available for reading from the device’s current read channel。
readyRead()對(duì)應(yīng)的槽函數(shù)為:
4)向服務(wù)器發(fā)送數(shù)據(jù)
QString data = ui->edtSend->toPlainText();if(data != ""){tcpClient->write(data.toLatin1()); //qt5去除了.toAscii()} 定義一個(gè)QString變量,從textEditor(edtSend)中獲取帶發(fā)送數(shù)據(jù),write()是QTcpSocket從QIODevice繼承的public function,直接調(diào)用就可以向服務(wù)器發(fā)送數(shù)據(jù)了。這里需要注意的是:toAscii()到qt5就沒(méi)有了,這里要寫(xiě)成toLatin1()。至此,通過(guò)4步,我們就完成了TCP Client的程序開(kāi)發(fā),源碼下載地址:客戶端qt程序源碼
二:服務(wù)器端編程
服務(wù)器段編程相比于客戶端要繁瑣一些,因?yàn)閷?duì)于客戶端來(lái)說(shuō),只能連接一個(gè)服務(wù)器。而對(duì)于服務(wù)器來(lái)說(shuō),它是面向多連接的,如何協(xié)調(diào)處理多客戶端連接就顯得尤為重要。
前言:編程過(guò)程中遇到的問(wèn)題 和 解決的方法
遇到的問(wèn)題:每個(gè)新加入的客戶端,服務(wù)器給其分配一個(gè)SocketDescriptor后,就會(huì)emit newConnection()信號(hào),但分配好的SocketDecriptor并沒(méi)有通過(guò)newConnection()信號(hào)傳遞,所以用戶得不到這個(gè)客戶端標(biāo)識(shí)SocketDescriptor。同樣的,每當(dāng)服務(wù)器收到新的消息時(shí),客戶端會(huì)emit readReady()信號(hào),然而readReady()信號(hào)也沒(méi)有傳遞SocketDescriptor, 這樣的話,服務(wù)器端即使接收到消息,也不知道這個(gè)消息是從哪個(gè)客戶端發(fā)出的。
解決的方法:
1. 通過(guò)重寫(xiě)[virtual protected] void QTcpServer::incomingConnection(qintptr socketDescriptor),獲取soketDescriptor。自定義TcpClient類(lèi)繼承QTcpSocket,并將獲得的soketDescriptor作為類(lèi)成員。 這個(gè)方法的優(yōu)點(diǎn)是:可以獲取到soketDescriptor,靈活性高。缺點(diǎn)是:需要重寫(xiě)函數(shù)、自定義類(lèi)。
2. 在newConnection()信號(hào)對(duì)應(yīng)的槽函數(shù)中,通過(guò)QTcpSocket *QTcpServer::nextPendingConnection()函數(shù)獲取 新連接的客戶端:Returns the next pending connection as a connected QTcpSocket object. 雖然仍然得不到soketDescriptor,但可以通過(guò)QTcpSocket類(lèi)的peerAddress()和peerPort()成員函數(shù)獲取客戶端的IP和端口號(hào),同樣是唯一標(biāo)識(shí)。 優(yōu)點(diǎn):無(wú)需重寫(xiě)函數(shù)和自定義類(lèi),代碼簡(jiǎn)潔。缺點(diǎn):無(wú)法獲得SocketDecriptor,靈活性差。
本文介紹第二種方法:
QT提供了QTcpServer類(lèi),可以直接實(shí)例化一個(gè)客戶端,可在help中索引如下:
The QTcpServer class provides a TCP-based server. More... Header: #include <QTcpServer> qmake: QT += network Inherits: QObject 從這里,我們可以看到,必須要在.pro文件中添加QT += network才可以進(jìn)行網(wǎng)絡(luò)編程,否則是訪問(wèn)不到<QTcpServer>頭文件的。我們看一下代碼頭文件: #ifndef MYTCPSERVER_H #define MYTCPSERVER_H#include <QMainWindow> #include <QTcpServer> #include <QTcpSocket> #include <QNetworkInterface> #include <QMessageBox> namespace Ui { class MyTcpServer; }class MyTcpServer : public QMainWindow {Q_OBJECTpublic:explicit MyTcpServer(QWidget *parent = 0);~MyTcpServer();private:Ui::MyTcpServer *ui;QTcpServer *tcpServer;QList<QTcpSocket*> tcpClient;QTcpSocket *currentClient;private slots:void NewConnectionSlot();void disconnectedSlot();void ReadData();void on_btnConnect_clicked();void on_btnSend_clicked();void on_btnClear_clicked(); };#endif // MYTCPSERVER_H 值得注意的是,在服務(wù)端編寫(xiě)時(shí),需要同時(shí)定義服務(wù)器端變量QTcpServer *tcpServer和客戶端變量 QList<QTcpSocket*> tcpClient。tcpSocket QList存儲(chǔ)了連接到服務(wù)器的所有客戶端。因?yàn)镼TcpServer并不是QIODevice的子類(lèi),所以在QTcpServer中并沒(méi)有任何有關(guān)讀寫(xiě)操作的成員函數(shù),讀寫(xiě)數(shù)據(jù)的操作全權(quán)交由QTcpSocket處理。1)初始化QTcpServer
tcpServer = new QTcpServer(this);ui->edtIP->setText(QNetworkInterface().allAddresses().at(1).toString()); //獲取本地IPui->btnConnect->setEnabled(true);ui->btnSend->setEnabled(false);connect(tcpServer, SIGNAL(newConnection()), this, SLOT(NewConnectionSlot())); 通過(guò)QNetworkInterface().allAddresses().at(1)獲取到本機(jī)IP顯示在lineEditor上(edtIP)。介紹如下: [static] QList<QHostAddress> QNetworkInterface::allAddresses() This convenience function returns all IP addresses found on the host machine. It is equivalent to calling addressEntries() on all the objects returned by allInterfaces() to obtain lists of QHostAddress objects then calling QHostAddress::ip() on each of these.:每當(dāng)新的客戶端連接到服務(wù)器時(shí),newConncetion()信號(hào)觸發(fā),NewConnectionSlot()是用戶的槽函數(shù),定義如下: void MyTcpServer::NewConnectionSlot() {currentClient = tcpServer->nextPendingConnection();tcpClient.append(currentClient);ui->cbxConnection->addItem(tr("%1:%2").arg(currentClient->peerAddress().toString().split("::ffff:")[1])\.arg(currentClient->peerPort()));connect(currentClient, SIGNAL(readyRead()), this, SLOT(ReadData()));connect(currentClient, SIGNAL(disconnected()), this, SLOT(disconnectedSlot())); } 通過(guò)nextPendingConnection()獲得連接過(guò)來(lái)的客戶端信息,取到peerAddress和peerPort后顯示在comboBox(cbxConnection)上,并將客戶端的readyRead()信號(hào)連接到服務(wù)器端自定義的讀數(shù)據(jù)槽函數(shù)ReadData()上。將客戶端的disconnected()信號(hào)連接到服務(wù)器端自定義的槽函數(shù)disconnectedSlot()上。2)監(jiān)聽(tīng)端口 與 取消監(jiān)聽(tīng)
bool ok = tcpServer->listen(QHostAddress::Any, ui->edtPort->text().toInt());if(ok){ui->btnConnect->setText("斷開(kāi)");ui->btnSend->setEnabled(true);} a)監(jiān)聽(tīng)端口的函數(shù):bool QTcpServer::listen(const QHostAddress &*address* = QHostAddress::Any, quint16 *port* = 0),該函數(shù)的作用為:Tells the server to listen for incoming connections on address *address* and port *port*. If port is 0, a port is chosen automatically. If address is QHostAddress::Any, the server will listen on all network interfaces. Returns true on success; otherwise returns false. for(int i=0; i<tcpClient.length(); i++)//斷開(kāi)所有連接{tcpClient[i]->disconnectFromHost();bool ok = tcpClient[i]->waitForDisconnected(1000);if(!ok){// 處理異常}tcpClient.removeAt(i); //從保存的客戶端列表中取去除}tcpServer->close(); //不再監(jiān)聽(tīng)端口 b)斷開(kāi)客戶端與服務(wù)器連接的函數(shù):disconnectFromHost()和waitForDisconnected()上文已述。斷開(kāi)連接之后,要將其從QList tcpClient中移除。服務(wù)器取消監(jiān)聽(tīng)的函數(shù):tcpServer->close()。 //由于disconnected信號(hào)并未提供SocketDescriptor,所以需要遍歷尋找for(int i=0; i<tcpClient.length(); i++){if(tcpClient[i]->state() == QAbstractSocket::UnconnectedState){// 刪除存儲(chǔ)在combox中的客戶端信息ui->cbxConnection->removeItem(ui->cbxConnection->findText(tr("%1:%2")\.arg(tcpClient[i]->peerAddress().toString().split("::ffff:")[1])\.arg(tcpClient[i]->peerPort())));// 刪除存儲(chǔ)在tcpClient列表中的客戶端信息tcpClient[i]->destroyed();tcpClient.removeAt(i);}} c)若某個(gè)客戶端斷開(kāi)了其與服務(wù)器的連接,disconnected()信號(hào)被觸發(fā),但并未傳遞參數(shù)。所以用戶需要遍歷tcpClient list來(lái)查詢每個(gè)tcpClient的state(),若是未連接狀態(tài)(UnconnectedState),則刪除combox中的該客戶端,刪除tcpClient列表中的該客戶端,并destroy()。3)讀取客戶端發(fā)送過(guò)來(lái)的數(shù)據(jù)
// 客戶端數(shù)據(jù)可讀信號(hào),對(duì)應(yīng)的讀數(shù)據(jù)槽函數(shù)void MyTcpServer::ReadData(){// 由于readyRead信號(hào)并未提供SocketDecriptor,所以需要遍歷所有客戶端for(int i=0; i<tcpClient.length(); i++){QByteArray buffer = tcpClient[i]->readAll();if(buffer.isEmpty()) continue;static QString IP_Port, IP_Port_Pre;IP_Port = tr("[%1:%2]:").arg(tcpClient[i]->peerAddress().toString().split("::ffff:")[1])\.arg(tcpClient[i]->peerPort());// 若此次消息的地址與上次不同,則需顯示此次消息的客戶端地址if(IP_Port != IP_Port_Pre)ui->edtRecv->append(IP_Port);ui->edtRecv->append(buffer);//更新ip_portIP_Port_Pre = IP_Port;}} 這里需要注意的是,雖然tcpClient產(chǎn)生了readReady()信號(hào),但readReady()信號(hào)并沒(méi)有傳遞任何參數(shù),當(dāng)面向多連接客戶端時(shí),tcpServer并不知道是哪一個(gè)tcpClient是數(shù)據(jù)源,所以這里遍歷tcpClient列表來(lái)讀取數(shù)據(jù)(略耗時(shí),上述的解決方法1則不必如此)。 讀操作由tcpClient變量處理:tcpClient[i]->readAll();4)向客戶端發(fā)送數(shù)據(jù)
//全部連接if(ui->cbxConnection->currentIndex() == 0){for(int i=0; i<tcpClient.length(); i++)tcpClient[i]->write(data.toLatin1()); //qt5除去了.toAscii()} a)向當(dāng)前連接的所有客戶端發(fā)數(shù)據(jù),遍歷即可。 //指定連接QString clientIP = ui->cbxConnection->currentText().split(":")[0];int clientPort = ui->cbxConnection->currentText().split(":")[1].toInt();for(int i=0; i<tcpClient.length(); i++){if(tcpClient[i]->peerAddress().toString().split("::ffff:")[1]==clientIP\&& tcpClient[i]->peerPort()==clientPort){tcpClient[i]->write(data.toLatin1());return; //ip:port唯一,無(wú)需繼續(xù)檢索}} b)在comboBox(cbxConnction)中選擇指定連接發(fā)送數(shù)據(jù):通過(guò)peerAddress和peerPort匹配客戶端,并發(fā)送。寫(xiě)操作由tcpClient變量處理:tcpClient[i]->write()。至此,通過(guò)4步,我們就完成了TCP Server的程序開(kāi)發(fā),源碼下載地址:服務(wù)器端qt程序源碼
總結(jié)
以上是生活随笔為你收集整理的QT TCP网络编程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: (转)关于SimpleDateForma
- 下一篇: [react] 浏览器为什么无法直接JS