C++ Qt開發:QUdpSocket網絡通信組件

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 個字節的數據。可選參數 addressport 用於返回數據報的源地址和端口號。如果不需要這些信息,可以將它們設置爲 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);
}

讀者可自行運行兩次客戶端,此時的端口將會隨機分配,當指定對端端口後就可以向其發送數據,如下圖所示;具體實現細節,請參考文章附件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章