当前位置: 代码网 > it编程>编程语言>C/C++ > Qt实现UDP单播、组播和广播功能

Qt实现UDP单播、组播和广播功能

2024年08月03日 C/C++ 我要评论
对象会选择其中一个地址来接收数据包。这个选择通常由操作系统或网络栈决定,并且可能会受到各种因素的影响,例如网络接口的优先级、路由表等。对象绑定到本机的所有 IPv4 地址,但实际上它只能通过其中一个地址接收数据包。然而,需要注意的是,绑定到多个 IPv4 地址并不意味着可以同时从多个地址接收数据包。对象可以接收通过本机的任意一个 IPv4 地址发送到指定端口的数据包。对象绑定到本机的所有 IPv4 地址。多网口的话,要指定唯一的网卡ip,之后加入组播。对象只能通过一个 IP 地址接收数据包。

一、udp单播、广播和组播的说明

udp是不可靠、无连接的,所以划分为发送方和接收方更好理解 

1、单播

udp是无连接的,进行单播通信时,必须要绑定接收方端口,发送方直接通过接收方的ip和绑定的端口进行通信。发送方可以绑定端口也可以不用绑定端口,不绑定端口的话,系统会随机分配端口。

对于多网卡来说,需要指定网卡,绑定一个网卡的ip。如果不进行显式的绑定操作,qudpsocket 对象将会使用默认的绑定方式,自动选择一个可用的 ip 地址进行绑定。

2、广播

对于只有1个网卡的主机来说,可以不用显示绑定ip,发送方直接发送广播就行,接收方绑定广播端口就行,这样才能看到收到的消息。 

对于多网卡来说,要指定唯一的网卡ip并且在广播前要绑定广播端口。ip可以不用绑定,这样系统会随机分配一个网卡ip

3、组播 

对于只有1个网卡的主机来说,可以不用绑定ip,直接绑定端口后加入组播就行。系统分配任意一个ip ,相当于 qhostaddress::anyipv4。

对于多网卡来说,要指定唯一的网卡并且在加入组播前要绑定组播端口,ip可以不用绑定,系统分配任意一个ip

注意:指定网卡不等于指定ip!!! 

1、使用 setmulticastinterface 方法可以指定一个明确的网卡,但并不意味着只有一个 ip 地址。一个网卡可以绑定多个 ip 地址,例如在同一台主机上同时存在有线网卡和无线网卡,它们可能都连接到同一局域网,并分别配置了不同的 ip 地址。此时,通过 setmulticastinterface 方法指定了一个明确的网卡后,并不确定使用哪个 ip 地址来进行组播通信。

如果需要确保使用特定的 ip 地址进行组播通信,则需要使用 bind 方法来将 qudpsocket 对象绑定到具体的 ip 地址和端口上,这样每次进行组播通信时,都会使用该 ip 地址来发送和接收数据报文。 

2、使用 qhostaddress::anyipv4 参数可以将 qudpsocket 对象绑定到本机的所有 ipv4 地址。这意味着,该 qudpsocket 对象可以接收通过本机的任意一个 ipv4 地址发送到指定端口的数据包。

然而,需要注意的是,绑定到多个 ipv4 地址并不意味着可以同时从多个地址接收数据包。在任何给定的时刻,qudpsocket 对象只能通过一个 ip 地址接收数据包。

当有多个 ipv4 地址可用时,qudpsocket 对象会选择其中一个地址来接收数据包。这个选择通常由操作系统或网络栈决定,并且可能会受到各种因素的影响,例如网络接口的优先级、路由表等。

因此,使用 qhostaddress::anyipv4 参数可以让 qudpsocket 对象绑定到本机的所有 ipv4 地址,但实际上它只能通过其中一个地址接收数据包。具体使用哪个地址取决于操作系统和网络环境。

二、遇到的udp通信的问题参考

      关于qt udp组播的几个问题icon-default.png?t=n7t8https://blog.csdn.net/tom06/article/details/52163665?spm=1001.2014.3001.5506

udp多播/组播通信,同一局域网下的两台机器通信接收不到数据icon-default.png?t=n7t8https://blog.csdn.net/qq_43290013/article/details/117288296?spm=1001.2014.3001.5506

qt读取网卡列表多网卡绑定组播网卡icon-default.png?t=n7t8https://blog.csdn.net/qq_30727593/article/details/127441711?spm=1001.2014.3001.5506

三、效果与代码

1、在.pro文件中添加如下内容:

qt       += network

2、在.h文件中添加串口所用的头文件

#include <qudpsocket>
#include <qnetworkinterface>

 3、添加一个qudpsocket* socket的类成员,并在.cpp中实例化对象:

socket = new qudpsocket;

 4、扫描可用网口

qlist<qnetworkinterface> interfacelist = qnetworkinterface::allinterfaces();
    foreach (qnetworkinterface nif, interfacelist) {
        // 检查网卡是否有效并已经启用
        if (nif.isvalid() && nif.flags().testflag(qnetworkinterface::isup)) {
            // 将已经启用的网卡名称添加到列表中
            enabledinterfacelist.append(nif);
            qlist<qnetworkaddressentry> entries = nif.addressentries();
            foreach (qnetworkaddressentry entry, entries) {
                if (entry.ip().protocol() == qabstractsocket::ipv4protocol) {
                    ui->nif_config->additem(entry.ip().tostring());
                }
            }
        }
    }

5、对组播进行设置(可以忽略)

//组播的数据的生存期,数据报没跨1个路由就会减1.表示多播数据报只能在同一路由下的局域网内传播
socket->setsocketoption(qabstractsocket::multicastttloption,1);
//1是允许loopback模式(自发自收),0是阻止。
socket->setsocketoption(qabstractsocket::multicastloopbackoption, true);

6、初始化组播设置

int mainwindow::init_group(qudpsocket *socket, qnetworkinterface &currentinterface, qhostaddress &localaddress, qhostaddress &targetaddress, int localport)
{
    if (targetaddress.ismulticast()) {//ismulticast()判断是否是组播地址
        if (localport == 0) {//判断是否指定本地端口
//bind(localaddress, qudpsocket::shareaddress | qudpsocket::reuseaddresshint)可以换成bind(qhostaddress::anyipv4, qudpsocket::shareaddress | qudpsocket::reuseaddresshint)
            if (socket->bind(localaddress, qudpsocket::shareaddress | qudpsocket::reuseaddresshint)) {
                socket->setmulticastinterface(currentinterface);
                if (socket->joinmulticastgroup(targetaddress, currentinterface)) {
                    qdebug() << "未指定本地端口,加入组播成功";
                    return 1;
                } else {
                    qdebug() << "未指定本地端口,加入组播失败";
                    return -1;
                }
            } else {
                qdebug() << "未指定本地端口,绑定失败";
                return -1;
            }
        } else {
            if (socket->bind(localaddress, localport, qudpsocket::shareaddress | qudpsocket::reuseaddresshint)) {
                socket->setmulticastinterface(currentinterface);
                if (socket->joinmulticastgroup(targetaddress, currentinterface)) {
                    qdebug() << "指定本地端口,加入组播成功";
                    return 1;
                } else {
                    qdebug() << "指定本地端口,加入组播失败";
                    return -1;
                }
            } else {
                qdebug() << "指定本地端口,绑定失败";
                return -1;
            }
        }
    } else {
        qdebug() << "目标地址不是组播地址";
        return -1;
    }

    // 如果执行到这里,说明没有通过任何返回语句,应该是一个错误
    qdebug() << "init_group 函数执行路径错误";
    return -1; // 或者抛出异常,取决于您希望如何处理这种情况
}

1、qudpsocket::shareaddress: 

  • 这个选项告诉操作系统允许多个 qudpsocket 对象绑定到同一个地址和端口上。在默认情况下,操作系统可能会阻止多个 socket 绑定到相同的地址和端口,但是如果设置了 shareaddress 选项,qt 会尝试在可能的情况下共享这个地址和端口。
  • 在实际应用中,如果需要多个 qudpsocket 对象同时监听相同的地址和端口,可以使用 shareaddress 选项来避免绑定失败的问题。
  • 想象一下你和朋友们想在同一个电话号码上收发短信。默认情况下,操作系统可能会阻止多个程序或者多个 qudpsocket 对象同时使用同一个网络地址(ip 地址)和端口号。但是如果你打开了 shareaddress 选项,就像是你和朋友们一起共享一个电话号码,大家可以同时收发信息。
  • 这个选项让多个 qudpsocket 对象可以在同一个网络地址和端口上工作,而不会相互干扰或者造成绑定失败的问题。

 2、qudpsocket::reuseaddresshint

  • 这个选项告诉操作系统允许在 udp socket 关闭之后,立即重新使用相同的地址和端口。如果没有设置这个选项,操作系统会在 qudpsocket 关闭后一段时间内保持端口的占用状态,这可能会导致稍后尝试重新绑定失败。
  • 设置 reuseaddresshint 可以避免在关闭一个 qudpsocket 后立即重新绑定时遇到 address already in use 的错误。
  • 想象一下你用一个电话号码打电话,然后挂了电话。在一段时间内,电话号码可能会被暂时保留,不允许其他人再用。这就好比默认情况下,当一个 qudpsocket 关闭后,操作系统可能会暂时保留使用的网络地址和端口号,不让其他程序立即使用。
  • 如果你设置了 reuseaddresshint,就像是告诉操作系统,“我关掉电话后,如果其他人想用这个电话号码,可以立刻用,不用等。” 这个选项允许在一个 qudpsocket 关闭后,立即重新使用相同的网络地址和端口号,而不会遇到“地址已经被占用”的错误。

7、发送消息

socket->writedatagram(str,targetaddress,targetport);

8、接收消息

//接收消息
connect(udpsocket,&qudpsocket::readyread,this,[&](){
    qbytearray datagram;
    datagram.resize(udpsocket->pendingdatagramsize());
    udpsocket->readdatagram(datagram.data(), datagram.size());
    ui->recvtextedit->append(datagram);
});

9、退出组播

int mainwindow::exit_group(qudpsocket *socket ,qnetworkinterface &currentinterface, qhostaddress &targetaddress)
{
    if(socket->leavemulticastgroup(targetaddress,currentinterface)){
        socket->abort();
        qdebug() << "退出组播成功";
        return 1;
    }else{
        qdebug() << "退出组播失败";
        return -1;
    }
}

四、自定义udp工具类

myudptools.h 

#ifndef myudptools_h
#define myudptools_h

#include <qobject>
#include <qthread>
#include <qcombobox>
#include <qudpsocket>
#include <qmessagebox>
#include <qapplication>
#include <qnetworkdatagram>
#include <qnetworkinterface>

class myudptools : public qobject
{
    q_object
public:
    explicit myudptools(qobject *parent = nullptr);
    ~myudptools();

    // 单播相关方法
    int setupunicast(qstring currentnetinterfacraddress, quint16 port = 0);
    void sendunicastdata(qstring targetaddress,quint16 port,const qbytearray &data);
    int exitunicast();

    // 组播相关方法
    int setupmulticast(qnetworkinterface *netinterface,qstring currentnetinterfacraddress,qstring groupaddress, quint16 port = 0);
    void sendmulticastdata(qstring targetaddress,quint16 port,const qbytearray &data);
    int exitmulticast();

    // 广播相关方法
    int setupbroadcast(qstring currentnetinterfacraddress,qstring targetaddress,quint16 port = 0);
    void sendbroadcastdata(qstring targetaddress,quint16 port,const qbytearray &data);
    int exitbroadcast();

    //添加或刷新combox的网卡信息
    void init_or_flash_netinterfaceinfotocombox(qlist<qnetworkinterface> &list,qcombobox *combox);

signals:
    void sig_recvdata(qbytearray data);

private slots:
    void processpendingdatagrams();//接收数据槽函数
    void handlesocketerror(qabstractsocket::socketerror socketerror);//udp错误日志

private:
    qthread *thread;
    qhostaddress localaddress;//本机当前网卡ip

    qudpsocket *unicastsocket;//单播
    quint16 unicastport_local;//单播本地端口
    quint16 unicastport_target;//单播目标端口
    qhostaddress targetaddress_unicast;//单播目标地址

    qudpsocket *multicastsocket;//组播
    qhostaddress multicastgroupaddress;//组播地址
    quint16 multicastport_local;//组播本地端口
    quint16 multicastport_target;//组播目标端口

    qudpsocket *broadcastsocket;//广播
    qhostaddress broadcastgroupaddress;//广播地址
    quint16 broadcastport_local;//广播本地端口
    quint16 broadcastport_target;//广播目标端口

};

#endif // myudptools_h

myudptools.cpp

#include "myudptools.h"

myudptools::myudptools(qobject *parent)
    : qobject(parent),unicastsocket(nullptr),multicastsocket(nullptr),broadcastsocket(nullptr)
{
    thread = new qthread();
    movetothread(thread);

    //应用程序关闭后触发
    connect(qapp,&qapplication::abouttoquit,thread, &qthread::quit);
    //线程退出后触发
    connect(thread, &qthread::finished, thread, &qthread::deletelater);
    connect(thread,&qthread::finished,this,&myudptools::deletelater);

    // 在子线程中直接调用函数
    qmetaobject::invokemethod(this, "init_or_flash_netinterfaceinfotocombox", qt::queuedconnection);

    //启动线程
    thread->start();
}

myudptools::~myudptools()
{
    if(unicastsocket){
        delete unicastsocket;
    }
    if(multicastsocket){
        delete multicastsocket;
    }
    if(broadcastsocket){
        delete broadcastsocket;
    }
}

//===========================单播相关方法实现===========================
int myudptools::setupunicast(qstring currentnetinterfacraddress, quint16 port)
{
    if (!unicastsocket) {
        unicastsocket = new qudpsocket(this);//将父对象设置为当前对象
        connect(unicastsocket, &qudpsocket::readyread, this, &myudptools::processpendingdatagrams);
        connect(unicastsocket, signal(error(qabstractsocket::socketerror)),this, slot(handlesocketerror(qabstractsocket::socketerror)));
        qinfo() << "创建单播socket";
    }
    localaddress = qhostaddress(currentnetinterfacraddress);
    unicastport_local = port;

    if(port == 0){
        if (!unicastsocket->bind(localaddress,qudpsocket::reuseaddresshint|qudpsocket::shareaddress)) {
            qcritical() << "单播绑定失败:" << unicastsocket->errorstring();
            return -1;
        }
    }else{
        if (!unicastsocket->bind(localaddress, unicastport_local,qudpsocket::reuseaddresshint|qudpsocket::shareaddress)) {
            qcritical() << "单播绑定失败:" << unicastsocket->errorstring();
            return -1;
        }
    }

    qinfo() << "单播连接成功";

    return 1;
}

void myudptools::sendunicastdata(qstring targetaddress, quint16 port, const qbytearray &data)
{
    if(unicastsocket){
        targetaddress_unicast = qhostaddress(targetaddress);
        unicastport_target = port;
        if(unicastsocket->writedatagram(data,targetaddress_unicast,unicastport_target) == -1){
            qcritical() << "发送单播数据失败:" << unicastsocket->errorstring();
        }
    }
}

int myudptools::exitunicast()
{
    if (unicastsocket) {
        unicastsocket->close();
        qcritical() << "退出成功";
        return 1;
    }else{
        qcritical() << "已退出单播,无需再退出";
        return -1;
    }
}

//===========================组播相关方法实现===========================
int myudptools::setupmulticast(qnetworkinterface *netinterface,qstring currentnetinterfacraddress, qstring groupaddress, quint16 port)
{
    if(!multicastsocket){
        multicastsocket = new qudpsocket(this);
        connect(multicastsocket, &qudpsocket::readyread, this, &myudptools::processpendingdatagrams);
        connect(multicastsocket, signal(error(qabstractsocket::socketerror)),this, slot(handlesocketerror(qabstractsocket::socketerror)));
        qinfo() << "创建组播socket";
    }
    localaddress = qhostaddress(currentnetinterfacraddress);
    multicastgroupaddress = qhostaddress(groupaddress);
    multicastport_local = port;

    if(!multicastgroupaddress.ismulticast()){
        qwarning() <<  "不是组播地址";
        return -1;
    }

    if(port == 0){
        if(!multicastsocket->bind(localaddress,qudpsocket::reuseaddresshint|qudpsocket::shareaddress)){
            qcritical() << "组播绑定失败:" << multicastsocket->errorstring();
            return -1;
        }
    }else{
        if(!multicastsocket->bind(localaddress,multicastport_local,qudpsocket::reuseaddresshint|qudpsocket::shareaddress)){
            qcritical() << "组播绑定失败:" << multicastsocket->errorstring();
            return -1;
        }
    }

    multicastsocket->setmulticastinterface(*netinterface);
    if(!multicastsocket->joinmulticastgroup(multicastgroupaddress,*netinterface)){
        qcritical() << "加入组播失败:" << multicastsocket->errorstring();
        return -1;
    }

    qinfo() << "组播连接成功";

    return 1;
}

void myudptools::sendmulticastdata(qstring targetaddress, quint16 port, const qbytearray &data)
{
    if(multicastsocket){
        multicastgroupaddress = qhostaddress(targetaddress);
        multicastport_target = port;
        if(multicastsocket->writedatagram(data,multicastgroupaddress,multicastport_target) == -1){
            qcritical() << "发送组播数据失败:" << multicastsocket->errorstring();
        }
    }
}

int myudptools::exitmulticast()
{
    if (multicastsocket) {
        multicastsocket->leavemulticastgroup(multicastgroupaddress);
        multicastsocket->close();
        qinfo() << "退出组播成功";
        return 1;
    }else{
        qcritical() << "已退出组播,无需再退出";
        return -1;
    }
}

//===========================广播相关方法实现===========================
int myudptools::setupbroadcast(qstring currentnetinterfaceaddress,qstring targetaddress, quint16 port)
{
    if (!broadcastsocket) {
        broadcastsocket = new qudpsocket(this);//将父对象设置为当前对象
        connect(broadcastsocket, &qudpsocket::readyread, this, &myudptools::processpendingdatagrams);
        connect(broadcastsocket, signal(error(qabstractsocket::socketerror)),this, slot(handlesocketerror(qabstractsocket::socketerror)));
        qinfo() << "创建广播socket";
    }
    localaddress = qhostaddress(currentnetinterfaceaddress);
    broadcastport_local = port;
    broadcastgroupaddress = qhostaddress(targetaddress);

    if(!broadcastgroupaddress.isbroadcast()){
        qcritical() << "不是广播地址";
        return -1;
    }

    if(port == 0){
        if (!broadcastsocket->bind(localaddress,qudpsocket::reuseaddresshint|qudpsocket::shareaddress)) {
            qcritical() << "广播绑定失败:" << broadcastsocket->errorstring();
            return -1;
        }
    }else{
        if (!broadcastsocket->bind(localaddress, broadcastport_local,qudpsocket::reuseaddresshint|qudpsocket::shareaddress)) {
            qcritical() << "广播绑定失败:" << broadcastsocket->errorstring();
            return -1;
        }
    }

    qinfo() << "连接成功";

    return 1;
}

void myudptools::sendbroadcastdata(qstring targetaddress, quint16 port, const qbytearray &data)
{
    if(broadcastsocket){
        broadcastgroupaddress = qhostaddress(targetaddress);
        broadcastport_target = port;
        if(broadcastsocket->writedatagram(data,broadcastgroupaddress,broadcastport_target) == -1){
            qcritical() << "发送广播数据失败:" << broadcastsocket->errorstring();
        }
    }
}

int myudptools::exitbroadcast()
{
    if (broadcastsocket) {
        broadcastsocket->close();
        qinfo() << "退出成功";
        return 1;
    }else{
        qdebug() << "已退出广播,无需再退出";
        return -1;
    }
}

//===========================添加或刷新combox的网卡信息函数实现===========================
void myudptools::init_or_flash_netinterfaceinfotocombox(qlist<qnetworkinterface> &list, qcombobox *combox)
{
    list.clear();
    combox->clear();

    qlist<qnetworkinterface> interfacelist = qnetworkinterface::allinterfaces();
    foreach (qnetworkinterface nif, interfacelist) {
        // 检查网卡是否有效并已经启用
        if (nif.isvalid() && nif.flags().testflag(qnetworkinterface::isup)) {
            // 将已经启用的网卡名称添加到列表中
            list.append(nif);
            qlist<qnetworkaddressentry> entries = nif.addressentries();
            foreach (qnetworkaddressentry entry, entries) {
                if (entry.ip().protocol() == qabstractsocket::ipv4protocol) {
                    combox->additem(entry.ip().tostring());
                }
            }
        }
    }
}

//===========================接收数据槽函数实现===========================
void myudptools::processpendingdatagrams()
{
    qudpsocket *socket = qobject_cast<qudpsocket *>(sender());//sender()发送信号的对象的指针
    if (!socket) return;

    while (socket->haspendingdatagrams()) {
#if 1
        qbytearray datagram;
        datagram.resize(socket->pendingdatagramsize());
        socket->readdatagram(datagram.data(), datagram.size());
        qdebug() << "received data:" << datagram;
        emit sig_recvdata(datagram);
#else
        qnetworkdatagram datagram = socket->receivedatagram();
        qbytearray data = datagram.data();
        emit sig_recvdata(data);
#endif
    }
}

//===========================udp错误日志信息===========================
void myudptools::handlesocketerror(qabstractsocket::socketerror socketerror)
{
    qudpsocket *socket = qobject_cast<qudpsocket *>(sender());
    if (!socket) return;

    switch (socketerror) {
    case qabstractsocket::hostnotfounderror:
        qcritical() << "host not found error:" << socket->errorstring();
        break;
    case qabstractsocket::connectionrefusederror:
        qcritical() << "connection refused error:" << socket->errorstring();
        break;
    case qabstractsocket::datagramtoolargeerror:
        qcritical() << "datagram too large error:" << socket->errorstring();
        break;
    default:
        qcritical() << "socket error:" << socket->errorstring();
        break;
    }
}

注意:

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com