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);
}
讀者可自行執行兩次客戶端,此時的埠將會隨機分配,當指定對端埠後就可以向其傳送資料,如下圖所示;具體實現細節,請參考文章附件。