Qt 是一個跨平臺C++圖形界面開發庫,利用Qt可以快速開發跨平臺窗體應用程序,在Qt中我們可以通過拖拽的方式將不同組件放到指定的位置,實現圖形化開發極大的方便了開發效率,本章將重點介紹如何運用QUdpSocket
組件實現基於UDP的網絡通信功能。
與QTcpSocket
組件功能類似,QUdpSocket
組件是 Qt 中用於實現用戶數據報協議(UDP,User Datagram Protocol)通信的類。UDP 是一種無連接的、不可靠的數據傳輸協議,它不保證數據包的順序和可靠性,但具有低延遲和簡單的特點。
以下是 QUdpSocket
類的完整函數及其簡要解釋:
函數 | 描述 |
---|---|
QUdpSocket(QObject *parent = nullptr) |
構造函數,創建一個新的 QUdpSocket 對象。 |
~QUdpSocket() |
析構函數,釋放 QUdpSocket 對象及其資源。 |
void bind(const QHostAddress &address, quint16 port, BindMode mode = DefaultForPlatform) |
將套接字綁定到指定的本地地址和端口。 |
void close() |
關閉套接字。 |
bool joinMulticastGroup(const QHostAddress &groupAddress, const QNetworkInterface &iface = QNetworkInterface()) |
加入多播組。 |
bool leaveMulticastGroup(const QHostAddress &groupAddress, const QNetworkInterface &iface = QNetworkInterface()) |
離開多播組。 |
qint64 pendingDatagramSize() const |
返回下一個待讀取的數據報的大小。 |
qint64 readDatagram(char *data, qint64 maxSize, QHostAddress *address = nullptr, quint16 *port = nullptr) |
讀取數據報。 |
QByteArray readDatagram(qint64 maxSize, QHostAddress *address = nullptr, quint16 *port = nullptr) |
讀取數據報,返回 QByteArray 對象。 |
qint64 writeDatagram(const char *data, qint64 size, const QHostAddress &address, quint16 port) |
發送數據報。 |
qint64 writeDatagram(const QByteArray &datagram, const QHostAddress &address, quint16 port) |
發送數據報,接受 QByteArray 對象。 |
QAbstractSocket::SocketState state() const |
返回套接字的當前狀態。 |
QAbstractSocket::SocketType socketType() const |
返回套接字的類型。 |
bool isValid() const |
如果套接字有效,則返回 true ;否則返回 false 。 |
int error() const |
返回套接字的當前錯誤代碼。 |
QHostAddress localAddress() const |
返回本地地址。 |
quint16 localPort() const |
返回本地端口。 |
int readBufferSize() const |
返回讀取緩衝區的大小。 |
void setReadBufferSize(int size) |
設置讀取緩衝區的大小。 |
QNetworkInterface multicastInterface() const |
返回多播組的網絡接口。 |
void setMulticastInterface(const QNetworkInterface &iface) |
設置多播組的網絡接口。 |
bool hasPendingDatagrams() const |
如果有待讀取的數據報,則返回 true ;否則返回 false 。 |
bool isReadable() const |
如果套接字可讀,則返回 true ;否則返回 false 。 |
bool isWritable() const |
如果套接字可寫,則返回 true ;否則返回 false 。 |
bool setSocketDescriptor(int socketDescriptor, QUdpSocket::SocketState socketState = ConnectedState, QIODevice::OpenMode openMode = ReadWrite) |
設置套接字描述符。 |
int socketDescriptor() const |
返回套接字描述符。 |
bool waitForReadyRead(int msecs = 30000) |
等待套接字可讀取數據。 |
bool waitForBytesWritten(int msecs = 30000) |
等待套接字已寫入指定字節數的數據。 |
void ignoreSslErrors(const QList<QSslError> &errors) |
忽略 SSL 錯誤。 |
void abort() |
強制關閉套接字。 |
QNetworkProxy proxy() const |
返回套接字的代理設置。 |
void setProxy(const QNetworkProxy &networkProxy) |
設置套接字的代理設置。 |
QString errorString() const |
返回套接字的錯誤消息字符串。 |
這些函數提供了在 UDP 通信中使用 QUdpSocket
的各種功能,包括綁定、發送和接收數據報、設置和獲取套接字的狀態等。
1.1 初始化部分
在初始化部分我們首先通過new QUdpSocket
來實現創建UDP對象,QUdpSocket
構造函數的函數原型如下:
QUdpSocket::QUdpSocket(QObject * parent = nullptr)
如上構造函數創建一個新的 QUdpSocket
對象。如果提供了 parent
參數,則會將新創建的 QUdpSocket
對象添加到 parent
對象的子對象列表中,並且在 parent
對象被銷燬時自動銷燬 QUdpSocket
對象。如果沒有提供 parent
參數,則 QUdpSocket
對象將不會有父對象,並且需要手動管理其生命週期。
初始化結束後,則下一步需要調用bind()
,bind()
函數是 QUdpSocket
類的一個成員函數,用於將套接字綁定到特定的本地地址和端口。它的函數原型如下:
void QUdpSocket::bind(const QHostAddress &address, quint16 port, BindMode mode = DefaultForPlatform)
address
:要綁定的本地地址,通常是QHostAddress::Any
,表示綁定到所有可用的網絡接口。port
:要綁定的本地端口號。mode
:綁定模式,指定套接字的行爲。默認值是DefaultForPlatform
,表示使用平臺默認的綁定模式。
該函數允許 QUdpSocket
在本地網絡接口上監聽傳入的數據報。一旦調用了 bind()
函數,QUdpSocket
就可以接收來自指定地址和端口的數據報。
在調用 bind()
函數之後,如果成功綁定了指定的地址和端口,套接字將處於 BoundState
狀態。如果出現錯誤,可以通過檢查 error()
函數獲取錯誤代碼,並通過 errorString()
函數獲取錯誤消息。
接着我們通過connect()
函數依次綁定套接字到stateChanged
狀態改變信號,以及readyRead()
讀取信號上,這段初始化代碼如下所示;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
udpSocket=new QUdpSocket(this);
// 生成隨機整數 包含2000 - 不包含65534
int randomInt = QRandomGenerator::global()->bounded(2000, 65534);
if(udpSocket->bind(randomInt))
{
this->setWindowTitle(this->windowTitle() + " | 地址: " + getLocalAddress() + " 綁定端口:" + QString::number(udpSocket->localPort()));
}
connect(udpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
onSocketStateChange(udpSocket->state());
connect(udpSocket,SIGNAL(readyRead()),this,SLOT(onSocketReadyRead()));
}
接着切換到讀取信號所對應的槽函數上,onSocketReadyRead
是我們自定義的一個槽,該槽函數功能如下所示;
// 讀取收到的數據報
void MainWindow::onSocketReadyRead()
{
while(udpSocket->hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
QHostAddress peerAddr;
quint16 peerPort;
udpSocket->readDatagram(datagram.data(),datagram.size(),&peerAddr,&peerPort);
QString str=datagram.data();
QString peer="[消息來自 " + peerAddr.toString()+":"+QString::number(peerPort)+"] | ";
ui->plainTextEdit->appendPlainText(peer+str);
}
}
首先在代碼中調用pendingDatagramSize
函數,pendingDatagramSize()
是 QUdpSocket
類的一個成員函數,用於獲取下一個待讀取的數據報的大小。它的函數原型如下:
qint64 QUdpSocket::pendingDatagramSize() const
該函數返回一個 qint64
類型的值,表示下一個待讀取的數據報的大小(以字節爲單位)。如果沒有待讀取的數據報,或者發生了錯誤,該函數將返回 -1。
通常,可以在調用 readDatagram()
函數之前調用 pendingDatagramSize()
函數來獲取下一個待讀取的數據報的大小。這樣可以爲數據緩衝區分配正確大小的空間,以確保完整地讀取數據報。
當有了待讀取字節後,接着就可以直接通過調用readDatagram
函數來從套接字中讀取數據報,readDatagram()
是 QUdpSocket
類的一個成員函數,它有幾個重載形式,其中最常用的是:
qint64 QUdpSocket::readDatagram(char * data, qint64 maxSize, QHostAddress * address = nullptr, quint16 * port = nullptr)
該函數用於讀取數據報並將其存儲到指定的緩衝區 data
中,最多讀取 maxSize
個字節的數據。可選參數 address
和 port
用於返回數據報的源地址和端口號。如果不需要這些信息,可以將它們設置爲 nullptr
。
函數返回實際讀取的字節數,如果發生錯誤,返回 -1。要查看錯誤信息,可以使用 error()
和 errorString()
函數。
另外,還有一個更簡單的重載形式:
QByteArray QUdpSocket::readDatagram(qint64 maxSize, QHostAddress * address = nullptr, quint16 * port = nullptr)
這個重載函數直接返回一個 QByteArray
對象,其中包含了讀取的數據報。
1.2 單播與廣播消息
單播(Unicast)和廣播(Broadcast)是網絡通信中常見的兩種數據傳輸方式,它們在數據包的傳輸範圍和目標數量上有所不同。
單播(Unicast)
單播是一種一對一的通信方式,其中數據包從一個發送者傳輸到一個接收者。在單播通信中,數據包只發送到目標主機的網絡接口,並且只有目標主機能夠接收和處理這個數據包。
- 一對一通信:每個數據包只有一個發送者和一個接收者。
- 目標明確:數據包只發送到特定的目標主機,其他主機不會接收到這個數據包。
- 點到點通信:適用於直接通信的場景,如客戶端與服務器之間的通信。
當按鈕發送消息被點擊後,則是一種單播模式,通常該模式需要得到目標地址與端口號,並通過調用writeDatagram
來實現數據的發送,該函數通過傳入三個參數,分別是發送字符串,目標地址與目標端口來實現一對一推送。
void MainWindow::on_pushButton_clicked()
{
QHostAddress targetAddr(ui->lineEdit_addr->text());
QString portString = ui->lineEdit_port->text();
quint16 targetPort = portString.toUShort();
QString msg=ui->lineEdit_msg->text();
QByteArray str=msg.toUtf8();
// 發送數據報
udpSocket->writeDatagram(str,targetAddr,targetPort);
ui->plainTextEdit->appendPlainText("[單播消息] | " + msg);
}
廣播(Broadcast)
廣播是一種一對多的通信方式,其中數據包從一個發送者傳輸到同一網絡中的所有主機。在廣播通信中,數據包被髮送到網絡中的所有主機,並且所有的主機都能夠接收和處理這個數據包。
- 一對多通信:每個數據包有一個發送者,但可以有多個接收者。
- 目標不明確:數據包被髮送到網絡中的所有主機,不需要知道接收者的具體地址。
- 廣播域:在局域網中進行廣播,只有在同一廣播域內的主機才能接收到廣播消息。
- 網絡負載:在大型網絡中使用廣播可能會產生大量的網絡流量,影響網絡性能。
當按鈕廣播消息被點擊後,則同樣是調用writeDatagram
函數與,唯一的區別在於第二個參數並未指定地址,而是使用了QHostAddress::Broadcast
來代替,意味着只要端口是一致的則對所有的客戶推送消息,其他保持不變。
void MainWindow::on_pushButton_2_clicked()
{
// 廣播地址
QString portString = ui->lineEdit_port->text();
quint16 targetPort = portString.toUShort();
QString msg=ui->lineEdit_msg->text();
QByteArray str=msg.toUtf8();
udpSocket->writeDatagram(str,QHostAddress::Broadcast,targetPort);
ui->plainTextEdit->appendPlainText("[廣播消息] | " + msg);
}
讀者可自行運行兩次客戶端,此時的端口將會隨機分配,當指定對端端口後就可以向其發送數據,如下圖所示;具體實現細節,請參考文章附件。