日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

QT TCP网络编程

發(fā)布時間:2023/12/9 c/c++ 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 QT TCP网络编程 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
首先介紹一下TCP:(Transmission Control Protocol 傳輸控制協(xié)議)是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議。相比而言UDP,就是開放式、無連接、不可靠的傳輸層通信協(xié)議。 下面,我一次進(jìn)行客戶端和服務(wù)器端的QT實現(xiàn)。我的開發(fā)環(huán)境是:QT Creator 5.7。

先看下效果圖:

一:客戶端編程

QT提供了QTcpSocket類,可以直接實例化一個客戶端,可在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ò)編程,否則是訪問不到<QTcpSocket>頭文件的。客戶端讀寫相對簡單,我們看一下代碼頭文件: #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 我們在窗口類中,定義了一個私有成員QTcpSoket *tcpClient。

1) 初始化QTcpSocket
在構(gòu)造函數(shù)中,我們需要先對其進(jìn)行實例化,并連接信號與槽函數(shù):

//初始化TCP客戶端tcpClient = new QTcpSocket(this); //實例化tcpClienttcpClient->abort(); //取消原有連接connect(tcpClient, SIGNAL(readyRead()), this, SLOT(ReadData()));connect(tcpClient, SIGNAL(error(QAbstractSocket::SocketError)), \this, SLOT(ReadError(QAbstractSocket::SocketError)));

2)建立連接 和 斷開連接

tcpClient->connectToHost(ui->edtIP->text(), ui->edtPort->text().toInt());if (tcpClient->waitForConnected(1000)) // 連接成功則進(jìn)入if{}{ui->btnConnect->setText("斷開");ui->btnSend->setEnabled(true);} a)建立TCP連接的函數(shù):void connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode = ReadWrite)是從QAbstractSocket繼承下來的public function,同時它又是一個virtual function。作用為:Attempts to make a connection to address on port port。 b)等待TCP連接成功的函數(shù):bool waitForConnected(int msecs = 30000)同樣是從QAbstractSocket繼承下來的public function,同時它又是一個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上的兩個lineEditor,用來填寫服務(wù)器IP和端口號。btnConnect是“連接/斷開”復(fù)用按鈕,btnSend是向服務(wù)器發(fā)送數(shù)據(jù)的按鈕,只有連接建立之后,才將其setEnabled。 tcpClient->disconnectFromHost();if (tcpClient->state() == QAbstractSocket::UnconnectedState \|| tcpClient->waitForDisconnected(1000)) //已斷開連接則進(jìn)入if{}{ui->btnConnect->setText("連接");ui->btnSend->setEnabled(false);} a)斷開TCP連接的函數(shù):void disconnectFromHost()是從QAbstractSocket繼承的public function,同時它又是一個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斷開連接函數(shù):bool waitForDisconnected(int msecs = 30000),同樣是從QAbstractSocket繼承下來的public function,同時它又是一個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ā)送過來的數(shù)據(jù)
readyRead()是QTcpSocket從父類QIODevice中繼承下來的信號:This signal is emitted once every time new data is available for reading from the device’s current read channel。
readyRead()對應(yīng)的槽函數(shù)為:

void MyTcpClient::ReadData() {QByteArray buffer = tcpClient->readAll();if(!buffer.isEmpty()){ui->edtRecv->append(buffer);} } readAll()是QTcpSocket從QIODevice繼承的public function,直接調(diào)用就可以讀取從服務(wù)器發(fā)過來的數(shù)據(jù)了。我這里面把數(shù)據(jù)顯示在textEditor控件上(ui>edtRecv)。由此完成了讀操作。 error(QAbstractSocket::SocketError)是QTcpSocket從QAbstractSocket繼承的signal, This signal is emitted after an error occurred. The socketError parameter describes the type of error that occurred.連接到的槽函數(shù)定義為: void MyTcpClient::ReadError(QAbstractSocket::SocketError) {tcpClient->disconnectFromHost();ui->btnConnect->setText(tr("連接"));QMessageBox msgBox;msgBox.setText(tr("failed to connect server because %1").arg(tcpClient->errorString())); xec(); } 這段函數(shù)的作用是:當(dāng)錯誤發(fā)生時,首先斷開TCP連接,再用QMessageBox提示出errorString,即錯誤原因。

4)向服務(wù)器發(fā)送數(shù)據(jù)

QString data = ui->edtSend->toPlainText();if(data != ""){tcpClient->write(data.toLatin1()); //qt5去除了.toAscii()} 定義一個QString變量,從textEditor(edtSend)中獲取帶發(fā)送數(shù)據(jù),write()是QTcpSocket從QIODevice繼承的public function,直接調(diào)用就可以向服務(wù)器發(fā)送數(shù)據(jù)了。這里需要注意的是:toAscii()到qt5就沒有了,這里要寫成toLatin1()。

至此,通過4步,我們就完成了TCP Client的程序開發(fā),源碼下載地址:客戶端qt程序源碼

二:服務(wù)器端編程
服務(wù)器段編程相比于客戶端要繁瑣一些,因為對于客戶端來說,只能連接一個服務(wù)器。而對于服務(wù)器來說,它是面向多連接的,如何協(xié)調(diào)處理多客戶端連接就顯得尤為重要。
前言:編程過程中遇到的問題 和 解決的方法
遇到的問題:每個新加入的客戶端,服務(wù)器給其分配一個SocketDescriptor后,就會emit newConnection()信號,但分配好的SocketDecriptor并沒有通過newConnection()信號傳遞,所以用戶得不到這個客戶端標(biāo)識SocketDescriptor。同樣的,每當(dāng)服務(wù)器收到新的消息時,客戶端會emit readReady()信號,然而readReady()信號也沒有傳遞SocketDescriptor, 這樣的話,服務(wù)器端即使接收到消息,也不知道這個消息是從哪個客戶端發(fā)出的。

解決的方法:
1. 通過重寫[virtual protected] void QTcpServer::incomingConnection(qintptr socketDescriptor),獲取soketDescriptor。自定義TcpClient類繼承QTcpSocket,并將獲得的soketDescriptor作為類成員。 這個方法的優(yōu)點是:可以獲取到soketDescriptor,靈活性高。缺點是:需要重寫函數(shù)、自定義類。
2. 在newConnection()信號對應(yīng)的槽函數(shù)中,通過QTcpSocket *QTcpServer::nextPendingConnection()函數(shù)獲取 新連接的客戶端:Returns the next pending connection as a connected QTcpSocket object. 雖然仍然得不到soketDescriptor,但可以通過QTcpSocket類的peerAddress()和peerPort()成員函數(shù)獲取客戶端的IP和端口號,同樣是唯一標(biāo)識。 優(yōu)點:無需重寫函數(shù)和自定義類,代碼簡潔。缺點:無法獲得SocketDecriptor,靈活性差。

本文介紹第二種方法:

QT提供了QTcpServer類,可以直接實例化一個客戶端,可在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ò)編程,否則是訪問不到<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ù)端編寫時,需要同時定義服務(wù)器端變量QTcpServer *tcpServer和客戶端變量 QList<QTcpSocket*> tcpClient。tcpSocket QList存儲了連接到服務(wù)器的所有客戶端。因為QTcpServer并不是QIODevice的子類,所以在QTcpServer中并沒有任何有關(guān)讀寫操作的成員函數(shù),讀寫數(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())); 通過QNetworkInterface().allAddresses().at(1)獲取到本機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ù)器時,newConncetion()信號觸發(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())); } 通過nextPendingConnection()獲得連接過來的客戶端信息,取到peerAddress和peerPort后顯示在comboBox(cbxConnection)上,并將客戶端的readyRead()信號連接到服務(wù)器端自定義的讀數(shù)據(jù)槽函數(shù)ReadData()上。將客戶端的disconnected()信號連接到服務(wù)器端自定義的槽函數(shù)disconnectedSlot()上。

2)監(jiān)聽端口 與 取消監(jiān)聽

bool ok = tcpServer->listen(QHostAddress::Any, ui->edtPort->text().toInt());if(ok){ui->btnConnect->setText("斷開");ui->btnSend->setEnabled(true);} a)監(jiān)聽端口的函數(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++)//斷開所有連接{tcpClient[i]->disconnectFromHost();bool ok = tcpClient[i]->waitForDisconnected(1000);if(!ok){// 處理異常}tcpClient.removeAt(i); //從保存的客戶端列表中取去除}tcpServer->close(); //不再監(jiān)聽端口 b)斷開客戶端與服務(wù)器連接的函數(shù):disconnectFromHost()和waitForDisconnected()上文已述。斷開連接之后,要將其從QList tcpClient中移除。服務(wù)器取消監(jiān)聽的函數(shù):tcpServer->close()。 //由于disconnected信號并未提供SocketDescriptor,所以需要遍歷尋找for(int i=0; i<tcpClient.length(); i++){if(tcpClient[i]->state() == QAbstractSocket::UnconnectedState){// 刪除存儲在combox中的客戶端信息ui->cbxConnection->removeItem(ui->cbxConnection->findText(tr("%1:%2")\.arg(tcpClient[i]->peerAddress().toString().split("::ffff:")[1])\.arg(tcpClient[i]->peerPort())));// 刪除存儲在tcpClient列表中的客戶端信息tcpClient[i]->destroyed();tcpClient.removeAt(i);}} c)若某個客戶端斷開了其與服務(wù)器的連接,disconnected()信號被觸發(fā),但并未傳遞參數(shù)。所以用戶需要遍歷tcpClient list來查詢每個tcpClient的state(),若是未連接狀態(tài)(UnconnectedState),則刪除combox中的該客戶端,刪除tcpClient列表中的該客戶端,并destroy()。

3)讀取客戶端發(fā)送過來的數(shù)據(jù)

// 客戶端數(shù)據(jù)可讀信號,對應(yīng)的讀數(shù)據(jù)槽函數(shù)void MyTcpServer::ReadData(){// 由于readyRead信號并未提供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()信號,但readReady()信號并沒有傳遞任何參數(shù),當(dāng)面向多連接客戶端時,tcpServer并不知道是哪一個tcpClient是數(shù)據(jù)源,所以這里遍歷tcpClient列表來讀取數(shù)據(jù)(略耗時,上述的解決方法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唯一,無需繼續(xù)檢索}} b)在comboBox(cbxConnction)中選擇指定連接發(fā)送數(shù)據(jù):通過peerAddress和peerPort匹配客戶端,并發(fā)送。寫操作由tcpClient變量處理:tcpClient[i]->write()。

至此,通過4步,我們就完成了TCP Server的程序開發(fā),源碼下載地址:服務(wù)器端qt程序源碼

總結(jié)

以上是生活随笔為你收集整理的QT TCP网络编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。