Qt下基于QUdpSocket实现指定源组播

这篇具有很好参考价值的文章主要介绍了Qt下基于QUdpSocket实现指定源组播。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  1. SSM指定源组播与ASM任意源组播基础概念

ASM-任意源组播,(IGMP-V2协议)

在这种模型下,任何发送方可以发送给任何组。在路由器角度上看,只要接收方“注册”了自己属于组播,任何发送方(任何源)的数据都会分到接收方。

SSM-指定源组播,(IGMP-V3协议)

接收方在“注册”自己加入组的同时,还会告诉路由器只接受某几个发送方(指定源),包括一个组地址和一个源IP地址。在这种模型下,其实任何发送方还是可以发送给任何组的。只是路由器会根据注册信息里的只把“合法源”的数据给到接收方。

从网络配置人员的角度看SSM避免了ASM部署的复杂性,从程序员角度看,SSM要比ASM麻烦一点点就是在加入组播的“注册”过程中,要把“源”的IP信息加进去。可能是我孤陋寡闻可能是Qt真的没考虑这个SSM的应用,至少从Qt4.8.X直到用的Qt5.9.X的Qt封装的QUdpSocket感觉没有支持的特别到位。

  1. .pro文件

除了network,windows还要多包一个LIBS += -lWs2_32


QT       += core gui network

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = D2dRecv
TEMPLATE = app

# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
win32 {
LIBS += -lWs2_32
}
unix{
}
SOURCES += \
        main.cpp \
        dialog.cpp

HEADERS += \
        dialog.h

FORMS += \
        dialog.ui
  1. 直接上代码

核心是用QUdpSocket的socketDescriptor()函数得到,最基础的socket号,让标准Socket的setsockopt设置“注册”过程,因为linux和windows的socket函数用的不一样,各实现以下就好了。

  1. Dialog.h

#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>
#include <QUdpSocket>
#include <QDateTime>

namespace Ui {
class Dialog;
}

class Dialog : public QDialog
{
    Q_OBJECT

public:
    explicit Dialog(QWidget *parent = 0);
    ~Dialog();
     QUdpSocket recvSock;//"QHostAddress::Any"""

     int AddSourceMembership(QUdpSocket& socket, QString groupIP, QString localIP, QStringList& groupSourceList);
     QHostAddress sendAdr;
     quint16 sendPort;
private slots:
    void on_pushButton_clicked();
    void on_pushButton_2_clicked();

public slots:
    void RecvFromServer();
private:
    Ui::Dialog *ui;
};

#endif // DIALOG_H
  1. Dialog.cpp

#include "dialog.h"
#include "ui_dialog.h"
#include <QMessageBox>

Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
    ui->setupUi(this);

}

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

//不论是linux还是windows,Qt目前都没有加入SSM的函数,只能自己通过C的socket操作补全,指定源
#ifdef WIN32
// 注意不可以在<winsock2.h>之前包含"windows.h",否则会导入"winsock.h",里面宏的定义不一致
//#include "windows.h"
#include <winsock2.h>
#include <ws2tcpip.h>
//如果是VC6还需要自己定义一个ip_mreq_source结构体和#define IP_ADD_SOURCE_MEMBERSHIP 15
//struct ip_mreq_source {
//  struct in_addr imr_multiaddr;
//  struct in_addr imr_sourceaddr;
//  struct in_addr imr_interface;
//};
#else
//linux下的实现基本类似,区别仅是包含的头文件不同。win10下Qt5.9没问题。银河麒麟4.0.2下Qt5.7没问题
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>

#define closesocket close

typedef int     BOOL;
typedef unsigned int DWORD;
typedef int     SOCKET;

#define INVALID_SOCKET -1
#define FALSE   0
#define TRUE    1
#define stricmp strcasecmp
#define ERROR_SUCCESS           0x0
#endif
// 添加指定源组播地址
int Dialog::AddSourceMembership(QUdpSocket& socket, QString groupIP, QString localIP, QStringList& groupSourceList)
{
    // inet_addr将字符串形式的IP转换为整数
    //为Socket设置组播Interface //绑定指定ip来接收组播组信息 Qt的QNetworkInterface也TM不好用,还是要回到C上
    in_addr       addr;
    addr.s_addr = inet_addr(localIP.toStdString().c_str());
    int ret=0;
    ret = setsockopt( socket.socketDescriptor(), IPPROTO_IP, IP_MULTICAST_IF,(const char*)&addr, sizeof(addr));
    QString firstip=groupSourceList.at(0);
    if(firstip.length()>4)
    {
        ip_mreq_source mcast;
#ifdef WIN32
        mcast.imr_interface.S_un.S_addr = inet_addr(localIP.toStdString().c_str());
        mcast.imr_multiaddr.S_un.S_addr = inet_addr(groupIP.toStdString().c_str());
#else

        mcast.imr_interface.s_addr = inet_addr(localIP.toStdString().c_str());
        mcast.imr_multiaddr.s_addr = inet_addr(groupIP.toStdString().c_str());
#endif
        // 多个组播源依次加入
        foreach (QString source, groupSourceList)
        {
            qDebug() << source;
#ifdef WIN32
            mcast.imr_sourceaddr.S_un.S_addr = inet_addr(source.toStdString().c_str());
#else
            mcast.imr_sourceaddr.s_addr = inet_addr(source.toStdString().c_str());
#endif
            ret = setsockopt(socket.socketDescriptor(), IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, (char*)&mcast, sizeof(mcast));
        }
    }else
    {
        struct ip_mreq mcast;
        mcast.imr_multiaddr.s_addr = inet_addr(groupIP.toStdString().c_str());
        mcast.imr_interface.s_addr = inet_addr(localIP.toStdString().c_str());
        ret = setsockopt(socket.socketDescriptor(), IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mcast, sizeof(mcast));
    }
    return ret;
}

void Dialog::RecvFromServer()
{
    char acDataBuf[65536];
    int iDataLen=65536;
    while (recvSock.hasPendingDatagrams())
    {
        memset(acDataBuf,0,65536);
        QHostAddress sendhost ;
        quint16 sendport = 0;
        int len = recvSock.readDatagram(acDataBuf, iDataLen,&sendhost,&sendport);
        if(len < 0)
        {
            continue;
        }else
        {
            QString test= acDataBuf;
            test+="\nsendIP:";
            test+=sendhost.toString();
            test+="\nsendPort:";
            test+=QString::number(sendport, 10);
            test+="\nrecvlength:";
            test+=QString::number(len, 10);
            QDateTime begin_time = QDateTime::currentDateTime();//获取系统现在的时间

            QString begin =begin_time .toString("\nyyyy.MM.dd hh:mm:ss.zzz ddd");
            test+=begin;
            ui->textEdit->setText(test);

            //模拟 接收后的处理、发送

            //            test+="\nsendTime:";
            //            QDateTime current_time = QDateTime::currentDateTime();
            //            QString currentTime = current_time.toString("yyyy-MM-dd hh:mm:ss.zzz ddd");
            //            test+=currentTime;
            //            QByteArray send_data=test.toUtf8();

            //            recvSock.writeDatagram(send_data,sendAdr,sendPort);
            //            ui->textEdit_2->setText(test);

        }
    }
}

void Dialog::on_pushButton_clicked()
{
    QHostAddress RecvAddress = QHostAddress(ui->lineEdit->text());
    quint16 RecvPort = ui->lineEdit_2->text().toInt();
    QHostAddress LocalAddress = QHostAddress(ui->lineEdit_3->text());

    QString SourceIPs = ui->lineEdit_4->text();
    QStringList SourceIPList =SourceIPs.split(";");

    //端口复用的形式绑定
    //recvSock.bind(LocalAddress,RecvPort,QUdpSocket::ShareAddress| QUdpSocket::ReuseAddressHint);
    //这个地方bind 什么跟操作系统的特性也TM有关系!AnyIPv4还是组播地址,无论如何理论上 不能是本地IP,不然就变单播了。
    bool ok  = recvSock.bind(QHostAddress::AnyIPv4,RecvPort,QUdpSocket::ShareAddress| QUdpSocket::ReuseAddressHint);

    if(!ok){
        QMessageBox::warning(NULL,"ERROR","UDP IP、端口Bind失败");
        recvSock.close();
        ui->pushButton->setEnabled(true);
        return ;
    }

    int isTrue  = AddSourceMembership(recvSock,RecvAddress.toString(),LocalAddress.toString(),SourceIPList);
    if(isTrue!=0)
    {
        QMessageBox::warning(NULL,"ERROR","UDP加入组播失败");
        recvSock.close();
        ui->pushButton->setEnabled(true);
        return ;
    }
    else
    {
        //设置ttl
        char ttl = 16;
        recvSock.setSocketOption(QAbstractSocket::MulticastTtlOption,ttl);
        //如果本地收不到,理论上还要设置一下Loop,我印象是不用,默认是开允许回环的。
        /*
假如在同一台计算机上有两个应用程序,并且加入了同一个组播。这两个程序,一个允许回环,一个阻断回环,则会有如下现象:
windows下,允许方不能发向阻断方,但阻断方可以发向允许方;
linux下,允许方可以发向阻断方,但阻断方不能发向允许方。
*/
        //设置Socket的接收缓冲区8M足够大了。
        recvSock.setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption,1024*1024*8);

        //基本上我能想的UDP组播的坑都总结写在这个程序里了。有机会再讨论。Hmy@2021.11.04
        connect(&recvSock, SIGNAL(readyRead()),this, SLOT(RecvFromServer()));

        //设置发送地址,同一个socket又发又收没毛病。
        sendAdr = QHostAddress(ui->lineEdit_5->text());
        sendPort=ui->lineEdit_6->text().toUShort();

        //如果一切顺利,就不要再搞一次设置了。
        ui->pushButton->setEnabled(false);
    }
}

void Dialog::on_pushButton_2_clicked()
{
    QByteArray send_data=ui->textEdit_2->toPlainText().toUtf8();
    int sendi = recvSock.writeDatagram(send_data,sendAdr,sendPort);
    qDebug() <<ui->textEdit_2->toPlainText()<<":"<<sendi;
}
  1. main.cpp

#include "dialog.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Dialog w;
    w.show();

    return a.exec();
}
  1. dialog.ui就算了

上传了资源直接下载吧,https://download.csdn.net/download/houmingyang/87610587

qt5.9能编过。

  1. 总结一下

本质上SSM这个事情不复杂,就是window下和linux下不太一样,甚至不同的linux版本里,对这协议的实现上感觉还是有细微的差距的。这个qt程序只是最简单的示意以下,很多具体问题要具体分析。其实都已经到直接些socket了我更倾向用原始的c/c++实现,有什么问题更容易发现和调整。qt封装的没问题,就是遇到稍微复杂具体问题的时候需要结合tcpdump的抓包具体分析调整。

PS:补充说下bind端口

  1. bind的时候不论如何不要用ip地址,有的操作系统支持bind组播地址,大部分保险还是用0.0.0.0吧。bind主要是绑定的端口,不是ip。除非想用udp单播。

  1. 设置组播用哪块网卡不是用bind函数,而是用选好的ip去设置socket的interface。

  1. 对于发送的socket不bind也可以,如果bind了,就不是系统随机分配发送端口了,而是用bind的端口发送了。

  1. 关于端口要有个基本概念:每个TCPIP的包,其实包含两个端口信息,一个是发送的socket的源端口(发送的时候如果不指定,系统会随机分配一个),一个是接收的socket要bind的目的端口。文章来源地址https://www.toymoban.com/news/detail-742222.html

到了这里,关于Qt下基于QUdpSocket实现指定源组播的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Qt 套接字类(QTcpSocket和QUdpSocket)解密:迈向 Qt 网络编程之巅

    套接字类在网络编程中起着至关重要的作用。套接字(Socket)为基于网络的通信提供了一种机制,使得不同设备、不同操作系统上的应用程序可以互相传输数据。套接字类负责建立连接、发送和接收数据、处理错误等任务,以简化网络通信的实现。通过使用套接字类,开发人

    2023年04月19日
    浏览(31)
  • C++ Qt开发:QUdpSocket网络通信组件

    Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用 QUdpSocket 组件实现基于UDP的网络通信功能。 与 QTcpSocket 组件功能类似

    2024年03月19日
    浏览(43)
  • Qt-udp(组播)

    2024年02月11日
    浏览(36)
  • QT读取网卡列表多网卡绑定组播网卡

    效果图: 初始化时执行了此函数,当网卡发生变化后再次选择网卡可能会导致程序崩溃。所以当网卡发生变化时 需要更新一下。 使用setMulticastInterface(face)函数来指定网卡 输入IP,绑定输入IP指定网卡,也可以通过选择网卡,直接绑定网卡

    2024年02月12日
    浏览(39)
  • Qt 重写QSlider简单实现滑动解锁控件(指定百分比回弹效果)

    组件效果图: 应用场景:  用于滑动解锁相关场景,Qt的控件鼠标监听机制对于嵌入式设备GUI可触摸屏依旧可用。 实现方式: 主要是通过继承QSlider以及搭配使用QStyleOptionSlider来实现效果。 注意细则: QStyleOptionSlider是用于定制空白区域是否可移动滑块,根据需求可舍弃。 组

    2024年02月07日
    浏览(38)
  • QT-基于Buildroot构建系统镜像下实现QT开发

    基于Build root编译整个镜像后,如何开发自己的基于QT的驱动小项目呢? 怎么编译QT,怎么测试?配置QT Creator繁琐?失败? 下面有一种比较简单的方法可供大家在学习时来参考使用。 对于驱动工程师来说,QT只是一种“手段”,我们主要的关注点应该集中在驱动程序本身的设

    2024年02月13日
    浏览(38)
  • 基于qt的简易聊天实现

    本次项目采用的是TCP传输文件,UDP实现聊天以及聊天状态的反馈。 一、首先运行程序会进入到这样一个会话界面,也就是新加入一个用户,新加入的用户会在右侧显示其用户名、主机名和IP地址,在消息记录框中也会提示在线信息。 二、消息字体样式、字体大小、加粗、斜体

    2024年02月11日
    浏览(36)
  • 基于Qt 文本读写(QFile/QTextStream/QDataStream)实现

    ​ 在很多时候我们需要读写文本文件进行读写,比如写个 Mp3 音乐播放器需要读 Mp3 歌词里的文本,比如修改了一个 txt 文件后保存,就需要对这个文件进行读写操作。本章介绍简单的文本文件读写,内容精简,让大家了解文本读写的基本操作。 ## QFile 读写文本 QFile 类提供了

    2024年02月06日
    浏览(36)
  • 实现对文件夹的动态检测功能——基于QT

    作者:小 琛 欢迎转载,请标明出处 个人遇到的需求:业务中,需要和其它模块对接,完成某类文件的生成、删除…等一系列操作,如果通过和其它模块定接口的方式,所需要的接口量很多并且所需要考虑的细节也很多。同时有一个很重要的点:这些文件是公用的,也就是说

    2024年02月09日
    浏览(39)
  • FFMpeg-3、基于QT实现音视频播放显示

    1、音视频播放的基础知识 内容来自雷神博客 1、在Windows平台下的视频播放技术主要有以下三种:GDI,Direct3D和OpenGL;音频播放技术主要是DirectSound。 SDL本身并不具有播放显示的功能,它只是封装了底层播放显示的代码 记录三种视频显示技术:GDI,Direct3D,OpenGL。其中Direct3D包

    2024年02月03日
    浏览(62)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包