Qt系列文章018-UDP组播

本文详细介绍了UDP组播的特性,包括组播地址的范围和使用,以及QUdpSocket在Qt中的实现。通过一个UDP组播实例程序,展示了如何加入和离开组播组,发送和接收组播数据报。程序设计包括了加入、退出组播的按钮功能,以及数据报的发送和接收操作。

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

1 前言

    上文因为篇幅和组播特性的问题,我只写了单播和广播的实例程序,所以这篇主打UDP组播的程序实现和相关讲解。

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

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

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

2 组播的特性

   在上一篇文章中,给出了组播示意图,简单表示了组播的原理。UDP组播是主机之间 一对一组 的通信模式,当多个客户端加入由一个组播地址定义的多播组之后,客户端向组播地址和端口发送的UDP数据报,组内成员都可以接收到,其功能类似于QQ群。

   组播报文的目的地址使用D类IP地址,D类地址不能出现在IP报文的源IP地址字段。用同一个IP多播地址接收多播数据报的所有主机构成了一个组,称为多播组(或组播组)。所有的信息接收者都加入到一个组内,并且- -旦加入之后,流向组地址的数据报立即开始向接收者传输,组中的所有成员都能接收到数据报。组中的成员是动态的,主机可以在任何时间加入和离开组。

   所以,采用UDP组播必须使用一个组播地址。组播地址是D类IP地址,有特定的地址段。多播组可以是永久的也可以是临时的。多播组地址中,有一部分由官方分配,称为永久多播组。永久多播组保持不变的是它的IP地址,组中的成员构成可以发生变化。永久多播组中成员的数量可以是任意的,甚至可以为零。那些没有保留下来的供永久多播组使用的IP组播地址,可以被临时多播组利用。关于组播IP地址,有如下的一些约定:

  • 224.0.0.0~ 224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0 保留不做分配,其他地址供路由协议使用;
  • 224.0. 1.0~ 224.0.1.255是公用组播地址,可以用于Internet;
  • 224.0.2.0 ~ 238.255 255.255为用户可用的组播地址( 临时组地址),全网范围内有效;
  • 239.0.0.0~239.255255255为本地管理组播地址,仅在特定的本地范围内有效。

   所以,若是在家庭或办公室局域网内测试UDP组播功能,可以使用的组播地址范围是239.0.0.0 ~ 239.255.255.255

    QUdpSocket支持UDP组播, joinMulticastGroup(函数使主机加入一个 多播组,leaveMulticastGroup() 函数使主机离开一个多播组,UDP组播的特点是使用组播地址,其他的端口绑定、数据报收发等功能的实现与单播UDP完全相同。

3 UDP组播实例程序讲解

    设计一个UDP组播实例程序 ,是基于前面UDP单播和广播上稍微改动的,这个和前面不同,不能再同一台机器上面跑多个实例后绑定端口和地址然后通信,组播需要在不同的ip地址的电脑上面玩,因为必须绑定的端口和地址是一致的,所以这边演示就会有点问题,只能靠各位大大自己去实践延时了,我只能跑通代码,如下图所示:
在这里插入图片描述
   设计的程序是可以发送和接收组播数据报,且在自己主机上发出的数据报,自己也可以接收到。

4 组播功能的程序实现

   还是和之前一样程序的主窗口是基于QWidget的类UDP,界面由UI设计器设计,其类定义如下(忽略UI设计器生成的槽函数):

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传入的数据
    void on_pushButton_JoinGroupCast_clicked();
    void on_pushButton_GroupCast_clicked();

    void on_pushButton_CleanMsgBox_clicked();

private:
    Ui::UDP *ui;

    QUdpSocket * mUdpSocket = nullptr;
    bool mUdpisJoinGroupCast= false; //udp是否已经绑定端口
    QHostAddress  mGroupAddress;  //组播地址
    
};
#endif // UDP_H

   其中定义了一个QHostAddress类型变量mGroupAddress,用于记录组播地址。下面是UDP的构造函数的代码:

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);
    }
    mUdpSocket->setSocketOption(QAbstractSocket::MulticastTtlOption, 1) ;

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

    QString localIP=getLocalIP();//本机 IP
    this->setWindowTitle (this->windowTitle() +"----本机IP: "+localIP) ;
    
    //随机4个局域网范围IP
    QStringList iPList = {"239.0.0.0", "239.0.1.1", "240.0.0.0", "241.0.10.1"};
    ui->comboIP->addItems(iPList);
    
    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;
}

   其中使用了QUdpSocket::setSocketOption()函数, 对socket 进行参数设置。
   将socketQAbstractSocket::MulticastTtlOption值设置为1。MulticastTtlOption 是UDP组播的数据报的生存期,数据报每跨1个路由会减1。缺省值为1,表示多播数据报只能在同一路由下的局域网内传播。
   要进行UDP组播通信,UDP客户端必须先加入UDP多播组,也可以随时退出多播组。主窗口上的加入组播退出组播按钮的代码如下:

void UDP::on_pushButton_JoinGroupCast_clicked()
{
    if(mUdpisJoinGroupCast)
    {
        //退出组播
        mUdpSocket->leaveMulticastGroup (mGroupAddress) ; //退出组播
        mUdpSocket->abort(); //解除绑定
        ui ->comboIP->setEnabled(true) ;
        ui->plainTextEdit->appendPlainText ("**已退出组播,解除端口绑定");
        ui->pushButton_JoinGroupCast->setText("加入组播");
    }
    else
    {
        //加入组播
        QString IP=ui ->comboIP->currentText() ;
        mGroupAddress=QHostAddress (IP) ;//多播组地址
        quint16 groupPort=ui->spinBox_Port->value();//端口
        if(mUdpSocket->bind(QHostAddress::AnyIPv4, groupPort, QUdpSocket::ShareAddress))
        {
            mUdpisJoinGroupCast = true;
            mUdpSocket->joinMulticastGroup (mGroupAddress); //加入多 播组
            ui->plainTextEdit->appendPlainText ("**加入组播成功") ;
            ui->plainTextEdit->appendPlainText ("**组播地址IP: "+IP) ;
            ui->plainTextEdit->appendPlainText ("**绑定端口: "+ QString::number(groupPort)) ;
            ui->comboIP->setEnabled(false) ;
            ui->pushButton_JoinGroupCast->setText("退出组播");
        }
        else
        {
            ui->plainTextEdit->appendPlainText ("**加入组播失败") ;
            ui->plainTextEdit->appendPlainText ("**绑定端口失败") ;
        }
    }
}

   加入组播之前,必须先绑定端口,绑定端口的语句是:

mUdpSocket->bind(QHostAddress::AnyIPv4, groupPort, QUdpSocket::ShareAddress)

   这里指定地址为QHostAddes::AnyIPv4,端口为多播组统一的一个端口。
   使用 QUdpSocket: joinMulticastGroup() 函数加入多播组,即:

mUdpSocket->joinMulticastGroup(mGroupAddress); 

   多播组地址groupAddress由界面上的组合框里输入。注意,局域网内的组播地址的范围是239.0.0.0~ 239.255.255.255,绝对不能使用本机地址作为组播地址。
   退出多播组,使用QUdpSocket:leaveMulticastGroup()函数,即:

mUdpSocket->leaveMulticastGroup(groupAddress);

   加入多播组后,发送组播数据报也是使用 writeDatagram() 函数, 只是目标地址使用的是组播地址,在 readyRead() 信号的槽函数里用 readDatagram() 读取数据报。下面是发送和读取数据报的代码:

void UDP::on_pushButton_GroupCast_clicked()
{
    //发送组播消息
    quint16 groupPort=ui->spinBox_Port->value() ;
    QString msg=ui ->lineEdit_editMsg->text () ;
    QByteArray datagram=msg. toUtf8() ;
    mUdpSocket->writeDatagram (datagram, mGroupAddress, groupPort) ;
    ui->plainTextEdit->appendPlainText (" [multicst] "+msg) ;
    ui->lineEdit_editMsg->clear() ;
    ui->lineEdit_editMsg->setFocus() ;
}


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) ;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值