Qt系列文章017-UDP单播广播

本文通过一个实例详细讲解了UDP通信中的单播、广播和组播功能,包括如何使用QUdpSocket进行绑定、发送和接收数据报。程序展示了在同一台计算机和不同计算机上运行时的通信方式,并提供了源代码实现单播和广播消息的发送。通过实际操作,加深了对UDP通信机制的理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 前言

    在前面讲解了UDP的基本概念后,还是要通过实例代码来全新过一遍才是真正的走通,实践出真知嘛,所以下面会用一个实例来分别讲解单播、广播、组播的区别和特点,其实,一般用UDP也就是单播用的对,不过多了解必有用处!

公众号:Qt实战,各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发。

公众号:Qt入门和进阶,专门介绍Qt/C++相关知识点学习,帮助Qt开发者更好的深入学习Qt。多位Qt元婴期大神,一步步带你从入门到进阶,走上财务自由之路。

官方店:https://shop114595942.taobao.com//

2 UDP通信实例程序功能

    老规矩,先新建一个UDP项目,如下图:
在这里插入图片描述
    项目名称就设为UDP,点击下一步生成项目,设计简单的界面构造,运行实例如下:
在这里插入图片描述
    实例程序UDP 实现单播、广播、组播功能,其主窗口是继承自QWidget的类,界面用UI设计器设计。程序可以进行UDP数据报的发送和接收,启动同样两个实例,他们之间可以进行UDP通信,这两个实例可以运行在同一台计算机上,也可以运行在不同的计算机上。

    在同一台计算机上运行时,两个运行实例需要绑定不同的端口,例如实例A绑定端口1001,实例B绑定端口1002。实例A向实例B 发送数据 报时,需要指定实例B所在主机的IP地址、绑定端口作为目标地址和目标端口,这样实例B才能 接收到数据报

    如果两个实例在不同计算机上运行,则可以使用相同的端口,因为IP地址不同了,不会导致绑定时发生冲突。一般的UDP通信程序都是在不同的计算机上运行的,约定一个固定的端口作为通信端口。

3 实例窗口类定义代码

    主窗口是基于 QWidget 的类UDP,界面采用UI设计器设计。UDP 类的定义如下(省略了UI设计器为actions和按钮生成的槽函数声明):

class UDP : public QWidget
{
    Q_OBJECT

public:
    UDP(QWidget *parent = nullptr);
    ~UDP();

    void initDataSlot();
    QString getLocalIP();
private slots:
    //自定义槽函数
    void onSocketStateChange(QAbstractSocket::SocketState socketState) ;
    void onSocketReadyRead() ; //读取socket传入的数据
private:
    Ui::UDP *ui;

    QUdpSocket * mUdpSocket = nullptr;
    bool mUdpisBind= false; //udp是否已经绑定端口
};
#endif // UDP_H

   QUdpSocket 类型的私有变量 mUdpSocket 是用于UDP通信的socket

   定义了两个自定义槽函数,onSocketStateChange() 与mUdpSocketstateChange() 信号关联,用于显示 mUdpSocket 当前的状态; onSocketReadyRead() 信号与udpSocket的 readyRead() 信号关联,用于读取缓冲区的数据报。

   UDP 的构造函数主要完成mUdpSocket创建、信号与槽函数的关联,代码如下:

UDP::UDP(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::UDP)
{
    ui->setupUi(this);
    initDataSlot();
}

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

void UDP::initDataSlot()
{
    if(mUdpSocket == nullptr)
    {
        mUdpSocket = new QUdpSocket(this);
    }

    ui->label_socketState->setText("Socket 状态:");

    QString localIP=getLocalIP();//本机 IP
    this->setWindowTitle (this->windowTitle() +"----本机IP: "+localIP) ;

    ui ->comboTargetIP->addItem(localIP) ;

    connect(mUdpSocket, &QUdpSocket::stateChanged, this, &UDP::onSocketStateChange);

    connect(mUdpSocket, &QUdpSocket::readyRead, this, &UDP::onSocketReadyRead);
}

QString UDP::getLocalIP()
{
    //获取本机IPv4地址
    QString hostName=QHostInfo::localHostName() ;//本地主机名
    QHostInfo hostInfo=QHostInfo::fromName (hostName) ;

    QString localIP="";
    QList<QHostAddress> addList=hostInfo.addresses () ;
    if (!addList. isEmpty())
    {
        for (int i=0; i <addList.count() ;i++)
        {
            QHostAddress aHost=addList.at(i) ;
            if (QAbstractSocket::IPv4Protocol == aHost. protocol())
            {
                localIP=aHost. toString() ;
                break;
            }
        }
    }

    return localIP;
}

   槽函数 onSocketStateChange() 的功能与上一章TCP通信程序里的完全一样, 不再显示其具体代码。

4 UDP通信的实现

   要实现UDP数据的接收,必须先用 QUdpSocket::bind() 函数绑定一个端口, 用于监听传入的数据报,解除绑定则使用 abort() 函数。程序主窗口,上的 绑定端口和解除绑定 按钮的响应代码如下:

void UDP::on_pushButton_bindingPort_clicked()
{
    if(mUdpisBind)
    {
        mUdpisBind = false;
        //解除绑定
        mUdpSocket->abort() ; //解除绑定
        ui->plainTextEdit->appendPlainText("**已解除绑定") ;
        ui->pushButton_bindingPort->setText("绑定端口");
        
    }
    else
    {
        //绑定端口
        quint16 port =ui->spinBox_bindingPort->value(); //本机UDP端口
        if (mUdpSocket->bind (port)) //绑定端口成功
        {
            mUdpisBind = true;
            ui->plainTextEdit->appendPlainText("**已成功绑定") ;
            ui->plainTextEdit->appendPlainText("**绑定端口:"+QString::number(mUdpSocket->localPort())) ;
            ui->pushButton_bindingPort->setText("解除绑定");
        }
        else{
            ui->plainTextEdit->appendPlainText("**绑定失败");
        }
    }
}

   绑定端口后,socket 的状态变为已绑定状态BoundState,解除绑定后状态变为未连接状态UnconnectedState

5 UDP单播、广播代码实现讲解

   单播消息和广播消息都使用 QUdpSocket::writeDatagram() 函数, 窗口上 单播消息广播消息两个按钮的代码如下:

void UDP::on_pushButton_unicast_clicked()
{
    //单播消息按钮.
    QString targetIP = ui->comboTargetIP->currentText(); //目标IP
    
    QHostAddress  targetAddr (targetIP) ;
    quint16 targetPort=ui->spinBox_Targetport->value() ;//目标port
    QString msg=ui->lineEdit_editMsg->text() ;//发送的消息内容
    QByteArray str=msg. toUtf8() ;
    mUdpSocket->writeDatagram (str, targetAddr, targetPort); //发出数据报
    ui->plainTextEdit->appendPlainText ("[out] "+msg) ;
    ui->lineEdit_editMsg->clear();
    ui ->lineEdit_editMsg->setFocus () ;
}

void UDP::on_pushButton_broadcast_clicked()
{
    //广播消息按钮.
    //quint16  targetPort=ui->spinBox_Targetport->value(); //目标端口

    //因为是一台电脑,所以端口不能相同,为了后面的测试效果写死广播目标范围
    quint16 targetPort[5] ={1001,1002,1003,1004,1005};

    QString  msg=ui->lineEdit_editMsg->text () ;
    QByteArray str=msg.toUtf8();

    for(auto port:targetPort){
         mUdpSocket->writeDatagram (str, QHostAddress::Broadcast, port) ;
    }

    ui->plainTextEdit->appendPlainText (" [broadcast] "+msg) ;
    ui->lineEdit_editMsg->clear () ;
    ui ->lineEdit_editMsg->setFocus () ;
}

   使用writeDatagram()函数向一个目标用户发送消息时,需要指定目标地址和端口。
   在广播消息时,只需将目标地址更换为一个特殊地址,即广播地址QHostdres:Broadeast,一般是 255.255.255.255

   QUdpSocket发送的数据报是QByteArray类型的字节数组,数据报的长度一般不超过 512 字节。数据报的内容可以是文本字符串,也可以自定义格式的二进制数据,文本字符串无需以换行符结束。
   QUdpSocket 接收到数据报后发射readyRead()信号,在关联的槽函数onSocketReadyRead() 里读取缓冲区的数据报,代码如下:

void UDP::onSocketReadyRead()
{
    //读取收到的数据报
    while (mUdpSocket->hasPendingDatagrams())
    {
        QByteArray datagram;
        datagram. resize (mUdpSocket->pendingDatagramSize()) ;
        QHostAddress peerAddr;
        quint16 peerPort;
        mUdpSocket->readDatagram (datagram. data(), datagram.size(), &peerAddr, &peerPort) ;
        QString str=datagram.data() ;
        QString peer="[From "+peerAddr. toString() +":" +QString::number (peerPort)+"] ";
        
        ui->plainTextEdit->appendPlainText (peer+str) ;
    }
}
  • hasPendingDatagrams()表示是否有待读取的传入数据报。

  • pendingDatagramSize()返回待读取数据报的字节数。

  • readDatagram()函数用于读取数据报的内容,其函数原型为:

    qint64 QUdpSocket::readDatagram(char *data, qint64 maxSize, QHostAddress *address = nullptr, quint16 *port = nullptr)
    

   输入参数datamaxSize是必须的,表示最多读取maxSize字节的数据到变量data里。addressport变量是可选的,用于获取数据报来源的地址和端口。上面的代码中使用了完整的参数形式,

   从而可以获得数据报来源的地址peerAddr和端口peerPort。如果无需获取来源地址和端口,可以采用简略形式,即:

udpSocket->readDatagram(datagram.data() , datagram.size());

   读取的数据报内容是QByteArray 字节数组,因为本程序只是传输字符串,所以简单地将其转换为字符串即可。如果传输的是自定义格式的字符串或二进制数据,需要对接收到的数据进行解析。


6 运行效果实践通信动态图

   因为组播和单播、广播有差异区别,加上篇幅到此感觉已经很长了,在写下去,恐怕读者会有疲劳感,反而阅读效果不佳, 所有暂时写到这里,UDP组播单独写成一章,到此单播和广播的实现也就结束了,下面看下单播和广播的通信动态图效果:
   
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值