Upd通信之QUdpSocket的unicast單播、broadcast廣播、multicast組播

簡 述: 瞭解Upd通信之QUdpSocketunicast單播、broadcast廣播、multicast組播,書寫一個簡單地例子;然後寫了一個小的Qt例子,用來實現和驗證它的空間的一些屬性和功能的用法。

本篇的csdn/github.io同步博文: Upd通信之QUdpSocket的unicast單播、broadcast廣播、multicast組播


系統環境:

編程環境: MacOS 10.14.6 (18G103) 編程軟件: Qt 5.9.8Qt Creator 4.8.2


QUdpSocket講解:

UDP通信是輕量的,不可靠(表示有概率會丟包),面向數據報,無連接的協議。用途可以比如:遠程視頻等

對於UDP通信而言,其實是沒有區分客戶端或者服務端的,應爲任何一個UdpSocket既可以看做客戶端,也可以看做服務端;這點與TCP的不一樣的。

另外就是QUdpSocket是以數據報的形式傳輸數據,而非連續的數據流。發送的數據報一般也都是QByteArray

類型的字節數組;數據報的長度一般是低於512字節的,且每一個數據報都是要包含有發送者和接受者的IPport等信息。

其中udp的消息傳播方式有如下三種:

  • unicast單播:

    一個udp客戶端只能夠發送數據報到另外一個指定的地址和端口的udp客戶端,是一對一的數據傳輸

  • broadcast廣播:

    一個udp客戶端發送的數據報,在同一網絡範圍內其他所有的udp客戶端都可以收到。其支持IPV4廣播📢,只需要將接收對象設置爲QHostAddress::Broadcast,且ip地址爲255.255.255.255(代表整個地址段的所有ip)

  • multicast組播:

    也被稱爲多播。就是相當於羣聊功能。udp客戶端加入到 另一個組播IP地址指定的多播組,成員向組播地址發送的數據報組內成員都可以接收到,使用QUdpSocket::joinMuliticastGroup()函數實現加入多播功能;加入多播後,UDP的發送和正常的UDP數據收發一樣。

而關於組播IP地址,是有着一些約定的:

綜上:若是在家庭或者辦公室或局域網中進行udp的測試,可以使用的組播地址是範圍是:239.0.0.0~238.255.255.255

QUdpSocket的主要常用接口:

函數 功能
bool bind(quint16 port = 0) 爲udp通信綁定一個端口
qint64 writeDatagram(QByteArray kdatagram, QHostAddress &host, quint16 port) 向目標地址和端口的udp客戶端發送數據報,返回成功發送的字節數
bool hasPendingDatagrams() 至少有一個數據報需要讀取的時,返回true
qint64 pendingDatagramSize() 返回第一個待讀取數據報的大小
qint64 readDatagram(char *data, qint64 maxSize) 讀取一個數據報,返回成功讀取的數據報的字節數
bool joinMulticastGroup(QHostAddress &groupAddress) 加入一個多播組
bool leaveMulticastGroup(QHostAddress &groupAddress) 離開一個多播組

unicast單播/broadcast廣播:

其中單播和組播的圖解如下:


multicast組播:

其中組播的關係圖如下:


運行效果:

這裏先放一張運行效果圖:

unicast單播/broadcast廣播:

multicast組播:


源碼分析:

其中核心部分的源碼,重點和一些難點以及需要注意的一些地方,貼出來如下

unicast單播/broadcast廣播:

其中.h頭文件:

#ifndef EXTRANS_H
#define EXTRANS_H

#include <QMainWindow>
#include <QLabel>
#include <QUdpSocket>
#include <QString>

namespace Ui {
class ExTrans;
}

/*!
 * \class ExTrans 一個UDP的Deam測試,同時測試單播和廣播
 * \brief 因爲是在同一臺電腦測試,所以IP相同,需要綁定兩個不同端口的,這樣不會衝突;
 * 若是兩臺電腦進行測試,那麼可以約定使用相同的端口號,使用不同的IP;來進行通訊
 */

class ExTrans : public QMainWindow
{
    Q_OBJECT

public:
    explicit ExTrans(QWidget *parent = nullptr);
    ~ExTrans();

private slots:
    void on_actBind_triggered();     //綁定端口
    void on_actDisbind_triggered();  //解除綁定
    void on_actClean_triggered();    //清除文本信息
    void on_actQuit_triggered();     //關閉程序
    void on_btnUnicast_clicked();    //單播消息
    void on_btnBroadcast_clicked();  //廣播消息

    void onSocketStateChange(QAbstractSocket::SocketState socketState);  //socket 狀態發生變化
    void onSocketReadyRead();        //讀取 socket 傳入的數據

private:
    QString getLocalIp();            //獲取本機IP

private:
    Ui::ExTrans *ui;

    QLabel* m_labSocketState;
    QUdpSocket* m_udpSocket;
};

#endif // EXTRANS_H

其中.cpp源文件:

#include "ExTrans.h"
#include "ui_ExTrans.h"

#include <QHostInfo>

ExTrans::ExTrans(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::ExTrans)
{
    ui->setupUi(this);
    setWindowTitle("Udp通信:unicast(單播) + broadcast(廣播)的使用");

    QString hostName = QHostInfo::localHostName();
    QString ip = getLocalIp();
    ui->plainTextEdit->appendPlainText("主機名稱:" + hostName + "\n主機IP:" + ip + "\n");

    m_labSocketState = new QLabel("Socket狀態:");
    m_labSocketState->setMinimumWidth(200);
    ui->statusBar->addWidget(m_labSocketState);

    ui->comboBoxIp->addItem(getLocalIp());
    m_udpSocket = new QUdpSocket(this);

    connect(m_udpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
    onSocketStateChange(m_udpSocket->state());
    connect(m_udpSocket, SIGNAL(readyRead()), this, SLOT(onSocketReadyRead()));
}

ExTrans::~ExTrans()
{
    delete ui;
}
//綁定端口
void ExTrans::on_actBind_triggered()
{
    quint16 port = ui->spinBoxBind->value();   //本機UDP端口
    if (m_udpSocket->bind(port)) {   //端口綁定成功
        ui->plainTextEdit->appendPlainText("端口綁定成功:" + QString::number(m_udpSocket->localPort()));

        ui->actBind->setEnabled(false);
        ui->actDisbind->setEnabled(true);
    } else {
        ui->plainTextEdit->appendPlainText("端口綁定失敗");
    }
}

//解除綁定
void ExTrans::on_actDisbind_triggered()
{
    m_udpSocket->abort();  //斷開,中止套接字
    ui->plainTextEdit->appendPlainText("端口解除綁定成功");

    ui->actBind->setEnabled(true);
    ui->actDisbind->setEnabled(false);
}

//清除文本信息
void ExTrans::on_actClean_triggered()
{
    ui->plainTextEdit->clear();
}

//關閉程序
void ExTrans::on_actQuit_triggered()
{
    close();
}

//單播消息
void ExTrans::on_btnUnicast_clicked()
{
    QString targetIp = ui->comboBoxIp->currentText();
    QHostAddress targetAddr(targetIp);    //目標 Ip
    quint16 targetPort = ui->spinBoxPort->value();   //目標 port
    QString msg = ui->lineEdit->text();   //發送的消息

    QByteArray str = msg.toUtf8();
    m_udpSocket->writeDatagram(str, targetAddr, targetPort);  //發送數據報

    ui->plainTextEdit->appendPlainText(QString("[Send: ] %1").arg(msg));
    ui->lineEdit->clear();
    ui->lineEdit->setFocus();
}

//廣播消息
void ExTrans::on_btnBroadcast_clicked()
{
    quint16 targetPort = ui->spinBoxPort->value();   //目標 port
    QString msg = ui->lineEdit->text();   //發送的消息

    QByteArray str = msg.toUtf8();
    m_udpSocket->writeDatagram(str, QHostAddress::Broadcast, targetPort);  //發送 數據報 給所有IP

    ui->plainTextEdit->appendPlainText(QString("[廣播: ] %1").arg(msg));
    ui->lineEdit->clear();
    ui->lineEdit->setFocus();
}

//socket 狀態發生變化
void ExTrans::onSocketStateChange(QAbstractSocket::SocketState socketState)
{
    switch (socketState) {
    case QAbstractSocket::UnconnectedState:
        m_labSocketState->setText("socket狀態:UnconnectedState");
        break;
    case QAbstractSocket::HostLookupState:
        m_labSocketState->setText("socket狀態:HostLookupState");
        break;
    case QAbstractSocket::ConnectingState:
        m_labSocketState->setText("socket狀態:ConnectingState");
        break;
    case QAbstractSocket::ConnectedState:
        m_labSocketState->setText("socket狀態:ConnectedState");
        break;
    case QAbstractSocket::BoundState:
        m_labSocketState->setText("socket狀態:BoundState");
        break;
    case QAbstractSocket::ClosingState:
        m_labSocketState->setText("socket狀態:ClosingState");
        break;
    case QAbstractSocket::ListeningState:
        m_labSocketState->setText("socket狀態:ListeningState");
        break;
    default:
        m_labSocketState->setText("socket狀態:其他未知狀態...");
        break;
    }
}

//讀取 socket 傳入的數據
void ExTrans::onSocketReadyRead()
{
    while (m_udpSocket->hasPendingDatagrams()) {
        QByteArray datagram;
        datagram.resize(m_udpSocket->pendingDatagramSize());

        QHostAddress peerAddr;
        quint16 peerPort;

        m_udpSocket->readDatagram(datagram.data(), datagram.size(), &peerAddr, &peerPort);  //讀取數據包,消息+來自的Ip和port
        QString str = datagram.data();
        QString peer = QString("[From: %1  %2] %3").arg(peerAddr.toString()).arg(QString::number(peerPort)).arg(str);
        ui->plainTextEdit->appendPlainText(peer);
    }
}

//獲取本機IP
QString ExTrans::getLocalIp()
{
    QString hostName = QHostInfo::localHostName();
    QHostInfo hostInfo = QHostInfo::fromName(hostName);
    QString Ip = "";

    if (hostInfo.addresses().isEmpty())
        return 0;

    foreach (QHostAddress addr, hostInfo.addresses()) {
        if (addr.protocol() == QAbstractSocket::IPv4Protocol) {
            Ip = addr.toString();
            break;
        }
    }

   return Ip;
}

multicast組播:

其中.h頭文件:

#ifndef EXMULTICAST_H
#define EXMULTICAST_H

#include <QMainWindow>
#include <QUdpSocket>
#include <QLabel>
#include <QHostInfo>
#include <QHostAddress>

namespace Ui {
class ExMulticast;
}

class ExMulticast : public QMainWindow
{
    Q_OBJECT

public:
    explicit ExMulticast(QWidget *parent = nullptr);
    ~ExMulticast();

private slots:
    void on_actStart_triggered();
    void on_actStop_triggered();
    void on_actClear_triggered();
    void on_actQuit_triggered();

    void onSocketStateChange(QAbstractSocket::SocketState socketState);  //socket 狀態發生變化
    void onSocketReadyRead();        //讀取 socket 傳入的數據
    void on_btnSend_clicked();

private:
    QString getLocalIp();            //獲取本機IP

private:
    Ui::ExMulticast *ui;

    QUdpSocket* m_udpSocket;      //用於通訊的 socket
    QLabel* m_labSocketState;
    QHostAddress m_groupAddress;  //組播地址
};

#endif // EXMULTICAST_H

其中.cpp源文件:

#include "ExMulticast.h"
#include "ui_ExMulticast.h"

ExMulticast::ExMulticast(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::ExMulticast)
{
    ui->setupUi(this);
    setWindowTitle("Udp通信:multicate(組播)的使用");

    QString hostName = QHostInfo::localHostName();
    QString ip = getLocalIp();
    ui->plainTextEdit->appendPlainText("主機名稱:" + hostName + "\n主機IP:" + ip + "\n");

    m_labSocketState = new QLabel("Socket狀態:");
    m_labSocketState->setMinimumWidth(200);
    ui->statusBar->addWidget(m_labSocketState);

    ui->comboBoxIp->addItem(getLocalIp());
    m_udpSocket = new QUdpSocket(this);     //用於通訊使用的 Socket
    //Multicast路由層次,1表示只在同一局域網內
    //組播TTL: 生存時間,每跨1個路由會減1,多播無法跨過大多數路由所以爲1
    //默認值是1,表示數據包只能在本地的子網中傳送。
    m_udpSocket->setSocketOption(QAbstractSocket::MulticastTtlOption, 1);



    connect(m_udpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
    onSocketStateChange(m_udpSocket->state());
    connect(m_udpSocket, SIGNAL(readyRead()), this, SLOT(onSocketReadyRead()));
}

ExMulticast::~ExMulticast()
{
    delete ui;
}

void ExMulticast::on_actStart_triggered()
{
    QString ip = ui->comboBoxIp->currentText();
    m_groupAddress = QHostAddress(ip);
    quint16 port = ui->spinBoxBind->value();

    if (m_udpSocket->bind(QHostAddress::AnyIPv4, port, QUdpSocket::ShareAddress)) {  //綁定端口
        m_udpSocket->joinMulticastGroup(m_groupAddress);  //加入多播組

        ui->plainTextEdit->appendPlainText("[用戶:" + getLocalIp() +"]  加入組播(組播地址:" + ip + " 端口:" + QString::number(port) + ")成功");
        ui->actStart->setEnabled(false);
        ui->actStop->setEnabled(true);
    } else {
        ui->plainTextEdit->appendPlainText("[用戶:" + getLocalIp() +"]  加入組播(組播地址:" + ip + " 端口:" + QString::number(port) + ")失敗");
    }
}

void ExMulticast::on_actStop_triggered()
{
    m_udpSocket->leaveMulticastGroup(m_groupAddress);  //退出組播
    m_udpSocket->abort();       //解除綁定
    ui->plainTextEdit->appendPlainText("[用戶:" + getLocalIp() +"]  退出組播成功");
    ui->actStart->setEnabled(true);
    ui->actStop->setEnabled(false);
}

void ExMulticast::on_actClear_triggered()
{
    ui->plainTextEdit->clear();
}

void ExMulticast::on_actQuit_triggered()
{
    close();
}

void ExMulticast::onSocketStateChange(QAbstractSocket::SocketState socketState)
{
    switch (socketState) {
    case QAbstractSocket::UnconnectedState:
        m_labSocketState->setText("socket狀態:UnconnectedState");
        break;
    case QAbstractSocket::HostLookupState:
        m_labSocketState->setText("socket狀態:HostLookupState");
        break;
    case QAbstractSocket::ConnectingState:
        m_labSocketState->setText("socket狀態:ConnectingState");
        break;
    case QAbstractSocket::ConnectedState:
        m_labSocketState->setText("socket狀態:ConnectedState");
        break;
    case QAbstractSocket::BoundState:
        m_labSocketState->setText("socket狀態:BoundState");
        break;
    case QAbstractSocket::ClosingState:
        m_labSocketState->setText("socket狀態:ClosingState");
        break;
    case QAbstractSocket::ListeningState:
        m_labSocketState->setText("socket狀態:ListeningState");
        break;
    default:
        m_labSocketState->setText("socket狀態:其他未知狀態...");
        break;
    }
}

void ExMulticast::onSocketReadyRead()
{
    while (m_udpSocket->hasPendingDatagrams()) {
        QByteArray datagram;
        datagram.resize(m_udpSocket->pendingDatagramSize());

        QHostAddress peerAddr;
        quint16 peerPort;

        m_udpSocket->readDatagram(datagram.data(), datagram.size(), &peerAddr, &peerPort);  //讀取數據包,消息+來自的Ip和port
        QString str = datagram.data();
        QString peer = QString("[From: %1  %2] %3").arg(peerAddr.toString()).arg(QString::number(peerPort)).arg(str);
        ui->plainTextEdit->appendPlainText(peer);
    }
}

QString ExMulticast::getLocalIp()
{
    QString hostName = QHostInfo::localHostName();
    QHostInfo hostInfo = QHostInfo::fromName(hostName);
    QString Ip = "";

    if (hostInfo.addresses().isEmpty())
        return 0;

    foreach (QHostAddress addr, hostInfo.addresses()) {
        if (addr.protocol() == QAbstractSocket::IPv4Protocol) {
            Ip = addr.toString();
            break;
        }
    }

   return Ip;
}

void ExMulticast::on_btnSend_clicked()
{
    quint16 port = ui->spinBoxBind->value();
    QString  msg = ui->lineEdit->text();
    QByteArray  datagram = msg.toUtf8();

    m_udpSocket->writeDatagram(datagram, m_groupAddress, port);
//    m_udpSocket->writeDatagram(datagram.data(), datagram.size(), m_groupAddress, port);
    ui->plainTextEdit->appendPlainText("[multicst] " + msg);
    ui->lineEdit->clear();
    ui->lineEdit->setFocus();
}


源碼下載:

https://github.com/touwoyimuli/QtExamples 【QtUdpEx】

發佈了168 篇原創文章 · 獲贊 205 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章