ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Qt Udp组播测试

Qt Udp组播测试 网络调试助手发送组播 使用Qt程序接收组播发送端:IP:10.65.13.84 端口6000接收端IP:10.65.13.83 端口4001加入组播:225.5.5.1接收方代码:OutIp :10.65.13.83#define IPA_OutSendIP 225.5.1.1 // 外部组播IP地址OutRecvSock new QUdpSocket(this); bool bBindStatus OutRecvSock-bind(QHostAddress::AnyIPv4,4001, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint); qDebug() __FILE__ __LINE__ bBindStatus; OutRecvSock-setSocketOption(QAbstractSocket::MulticastTtlOption, 48);//获取IP地址的网卡 QListQNetworkInterface interList QNetworkInterface::allInterfaces(); int index -1; int i 0; foreach (QNetworkInterface inter,interList) { foreach (QNetworkAddressEntry entry, inter.addressEntries()) { QHostAddress ip entry.ip(); if(ip QHostAddress(OutIP)) { index i; break; } if(index ! -1) { break; } } i; } if(index ! -1) { // 加入组播 bool b OutRecvSock-joinMulticastGroup(QHostAddress(IPA_OutSendIP),interList[index]); if(b) { qDebug()join Listen group success interList[index].name(); } }onnect(OutRecvSock,QUdpSocket::readyRead,this,OuterNetWorker::onOutMsg);void OuterNetWorker::onOutMsg()// 接收 { while(OutRecvSock-hasPendingDatagrams()) { QByteArray datagram; datagram.resize(OutRecvSock-pendingDatagramSize()); QHostAddress sender; quint16 port; OutRecvSock-readDatagram(datagram.data(),datagram.size(),sender,port); qDebug() datagram.toHex(); } }现在使用Qt来编写发送端代码使用网络调试助手来接收组播报文。发送端:IP:10.65.13.83 端口24576接收端IP:10.65.13.84 端口24584组播地址225.5.1.1OutIp :10.65.13.83#define IPA_OutSendIP 225.5.1.1 // 外部发送组播IP地址 #define IPA_OutSendPort 24584 // 外部发送组播端口号// 外引导 // Socket OutSendSock new QUdpSocket(this); OutSendSock-bind(QHostAddress(OutIP),24576, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint); OutSendSock-setSocketOption(QAbstractSocket::MulticastTtlOption, 48); //获取IP地址的网卡 QListQNetworkInterface interList QNetworkInterface::allInterfaces(); int index -1; int i 0; foreach (QNetworkInterface inter,interList) { foreach (QNetworkAddressEntry entry, inter.addressEntries()) { QHostAddress ip entry.ip(); if(ip QHostAddress(OutIP)) { index i; break; } if(index ! -1) { break; } } i; } if(index ! -1) { // 加入组播 bool b OutSendSock-joinMulticastGroup(QHostAddress(IPA_OutSendIP),interList[index]); if(b) { qDebug()OutSendSock join Listen group success interList[index].name(); } }//发送组播 QByteArray ba data; if(OutSendSock) { quint32 len OutSendSock-writeDatagram(ba,QHostAddress(IPA_OutSendIP),IPA_OutSendPort); }在组播的流程中接收与发送都需要指定端口号。为什么这是一个非常核心的计算机网络问题。简单直接的回答是IP地址包括组播IP负责找到“哪台机器”而端口号负责找到“机器上的哪个程序”进程。在组播场景下发送和接收都必须指定端口号是为了完成网络层的寻址与传输层的进程标识的分离。具体原因可以从发送端和接收端两个角度来看1. 发送端为什么需要指定端口号发送端实际上需要指定两个端口但通常我们常说的是目标端口。目标端口必须发送端的writeDatagram(data, groupAddress, port)中的port就是目标端口。接收端必须绑定在这个端口上发送端才能把数据送过去。这相当于信封上的“收件人部门”操作系统看到这个端口号才知道该把数据包交给机器上哪个正在监听的应用进程。源端口可选但强烈建议如果不手动绑定系统会自动分配一个临时端口。但如果接收端需要给发送端回传数据如ACK确认就需要发送端提供源端口。因此在实际通信中发送端通常会bind()一个固定端口既作为源端口也方便接收数据。2. 接收端为什么需要指定端口号绑定接收端必须调用bind(port)来监听一个特定的端口。原因在于操作系统是多任务并行的同一台电脑上同时运行着浏览器端口443、游戏端口27015和你的组播程序。当网卡收到一个 UDP 数据包时操作系统内核需要根据数据包中的“目标端口号”决定把数据递给哪个进程。注册机制bind()的本质是向操作系统内核注册“凡是发往目标IP:该端口的包都交给我的套接字”。如果接收端不绑定端口内核收到组播包后就找不到接收者会直接丢弃。3. 特别澄清组播IP与端口的关系初学者容易混淆“加入组播组”和“绑定端口”加入组播组joinMulticastGroup是针对IP 层的操作。它告诉交换机/路由器本机想接收发往239.255.43.21这个 IP 地址的数据包。绑定端口bind是针对传输层的操作。它告诉操作系统将发往12345端口的数据递交给该套接字。两者缺一不可如果只加入组播组而不绑定端口数据包到了网卡但操作系统不知道给哪个程序如果只绑定端口而不加入组播组路由器不会把组播流量转发给这台机器也收不到包。4. 有趣的现象端口复用正因为端口是“进程门牌号”所以会出现以下情况可以帮助你加深理解不同组播地址可以共用同一个端口比如程序A监听239.0.0.1:9999程序B监听239.0.0.2:9999。两台程序可以同时运行因为虽然端口都是9999但操作系统根据五元组源IP、源端口、目标IP、目标端口、协议能区分开。此时在 Qt 中需要开启QUdpSocket::ShareAddress模式。同一个组播地址不同端口互不干扰这就像同一个小区组播IP有不同的楼栋号端口快递员数据包根据楼栋号送信互不影响。一句话总结组播 IP 解决的是“数据包该去哪台主机或哪些主机”端口号解决的是“数据包到主机后该找哪个应用程序”。两者结合才能完成从网络到进程的最终交付。解析代码//获取IP地址的网卡 QListQNetworkInterface interList QNetworkInterface::allInterfaces(); int index -1; int i 0; foreach (QNetworkInterface inter,interList) { foreach (QNetworkAddressEntry entry, inter.addressEntries()) { QHostAddress ip entry.ip(); if(ip QHostAddress(OutIP)) { index i; break; } if(index ! -1) { break; } } i; }这段代码的目标是遍历系统中所有网络接口找到与指定 IP 地址变量OutIP匹配的那个接口并记录其在列表中的索引。//获取IP地址的网卡 QListQNetworkInterface interList QNetworkInterface::allInterfaces(); qDebug() __FILE__ __LINE__ interList.count(); foreach (QNetworkInterface inter,interList) { qDebug() __FILE__ __LINE__ inter.name() inter.type(); }../../../LPL/T/ttt/main1.cpp 12 7 ../../../LPL/T/ttt/main1.cpp 15 lo QNetworkInterface::Loopback ../../../LPL/T/ttt/main1.cpp 15 sit0 QNetworkInterface::Unknown ../../../LPL/T/ttt/main1.cpp 15 ip6tnl0 QNetworkInterface::Virtual ../../../LPL/T/ttt/main1.cpp 15 can0 QNetworkInterface::CanBus ../../../LPL/T/ttt/main1.cpp 15 can1 QNetworkInterface::CanBus ../../../LPL/T/ttt/main1.cpp 15 can2 QNetworkInterface::CanBus ../../../LPL/T/ttt/main1.cpp 15 enp5s0 QNetworkInterface::Ethernet输出结果各接口含义接口名type()返回类型说明loLoopback本地回环接口127.0.0.1仅用于本机通信不能用于组播发送到网络。sit0UnknownIPv6-in-IPv4 隧道接口Simple Internet Transition用于 IPv6 过渡技术一般不用于普通组播。ip6tnl0VirtualIPv6 隧道接口IPv6 tunnel同样属于虚拟隧道不参与局域网组播。can0/can1/can2CanBusCAN 总线接口Controller Area Network常见于汽车电子或工业控制不是 IP 网络接口不能用于 UDP 组播。enp5s0Ethernet有线以太网网卡这是你真正需要的物理网卡可用来发送/接收 UDP 组播。QNetworkInterface::InterfaceFlagIsUp0x1接口已启用管理状态为 up必须条件。如果网卡被ifconfig down停用不能发数据。IsRunning0x2接口已分配资源驱动就绪、物理链路通常已通强烈建议。代表系统已准备好收发数据包。有时IsUp为真但IsRunning为假如网线没插此时发送会失败。CanBroadcast0x4支持广播模式如以太网参考项。通常物理网卡都支持但组播不依赖广播所以非必须。IsLoopBack0x8回环接口即127.0.0.1必须排除除非你只想本机进程间通信。发往组播地址的数据包走回环接口不会到达局域网。IsPointToPoint0x10点对点接口如 PPP 拨号、VPN 隧道谨慎对待。某些 VPN如 OpenVPN 的 tun 模式支持组播但大多数情况下组播无法穿越点到点链路一般建议排除或让用户手动选择。CanMulticast0x20支持 IP 组播核心必须条件这是操作系统明确告诉你的这个接口支持加入组播组和发送组播包。改进后的代码://获取IP地址的网卡 QListQNetworkInterface interList QNetworkInterface::allInterfaces(); qDebug() __FILE__ __LINE__ interList.count(); foreach (QNetworkInterface inter,interList) { QNetworkInterface::InterfaceFlags flags inter.flags(); qDebug() __FILE__ __LINE__ inter.name() inter.type() flags; // 1. 必须处于启用且运行状态 if (!(flags QNetworkInterface::IsUp) || !(flags QNetworkInterface::IsRunning)) { continue; } // 2. 必须支持组播核心条件 if (!(flags QNetworkInterface::CanMulticast)) { continue; } // 3. 排除回环接口 if (flags QNetworkInterface::IsLoopBack) { continue; } // 4. (强烈建议) 排除点对点接口避免无效发送 if (flags QNetworkInterface::IsPointToPoint) { continue; } qDebug() __FILE__ __LINE__; }../../../LPL/T/ttt/main1.cpp 12 7 ../../../LPL/T/ttt/main1.cpp 16 lo QNetworkInterface::Loopback QFlagsQNetworkInterface::InterfaceFlag(IsUp|IsRunning|IsLoopBack) ../../../LPL/T/ttt/main1.cpp 16 sit0 QNetworkInterface::Unknown QFlagsQNetworkInterface::InterfaceFlag() ../../../LPL/T/ttt/main1.cpp 16 ip6tnl0 QNetworkInterface::Virtual QFlagsQNetworkInterface::InterfaceFlag() ../../../LPL/T/ttt/main1.cpp 16 can0 QNetworkInterface::CanBus QFlagsQNetworkInterface::InterfaceFlag() ../../../LPL/T/ttt/main1.cpp 16 can1 QNetworkInterface::CanBus QFlagsQNetworkInterface::InterfaceFlag() ../../../LPL/T/ttt/main1.cpp 16 can2 QNetworkInterface::CanBus QFlagsQNetworkInterface::InterfaceFlag() ../../../LPL/T/ttt/main1.cpp 16 enp5s0 QNetworkInterface::Ethernet QFlagsQNetworkInterface::InterfaceFlag(IsUp|IsRunning|CanBroadcast|CanMulticast) ../../../LPL/T/ttt/main1.cpp 37进一步在循环的末尾添加打印:foreach (QNetworkAddressEntry entry, inter.addressEntries()) { QHostAddress ip entry.ip(); qDebug() ip; }QHostAddress(10.65.13.83) QHostAddress(fe80::4166:f91e:8e5:9b20%enp5s0)地址类型作用域特点10.65.13.83IPv4 私有地址全局/站点依赖路由全网唯一在局域网内可直接用于比较。fe80::4166:f91e:8e5:9b20%enp5s0IPv6 链路本地地址仅限本链路同一网段%enp5s0是作用域IDScope ID它告诉操作系统这个地址属于哪个网卡。因为fe80::/10地址段在所有网卡上都存在每块网卡都有一个没有%enp5s0这个地址在全球范围内是不唯一的。fe80::4166:f91e:8e5:9b20%enp5s0 这是什么这是一个IPv6 链路本地地址Link-Local IPv6 Address。为了让非网络专业的开发者也能完全理解我们把它拆成三部分来看fe80::4166:f91e:8e5:9b20%enp5s0地址部分fe80::4166:f91e:8e5:9b20fe80::/10前缀这是 IPv6 中专门保留给“链路本地”使用的地址段。只要你的电脑开启了 IPv6每块网卡都会自动生成一个以fe80开头的地址不需要 DHCP 服务器分配也不需要手动配置。后半部分通常是由网卡的MAC 地址通过算法EUI-64生成的或者是随机生成的隐私扩展。它的作用是确保在同一根网线同一链路里这块网卡的身份是唯一的。核心特性这个地址只能在当前物理链路比如同一个交换机下的局域网内有效。路由器绝对不会转发以fe80开头的包所以它不能用于互联网通信只用于局域网内的底层协议如自动发现、邻居探测、DHCPv6 请求等。作用域 IDScope ID%enp5s0这是最容易让新手困惑的地方也是最关键的部分。为什么需要%后缀因为fe80::地址是“链路本地”的。想象一下你的电脑上可能同时有有线网卡enp5s0、Wi-Fiwlan0、虚拟机网卡vmnet。因为每个网卡都会自动生成一个fe80::地址所以单纯说“发数据给fe80::4166:...”是有歧义的——操作系统不知道你要发给哪个网卡相连的设备。%enp5s0的作用它就是一个“网卡标识符”告诉操作系统“这个地址是在enp5s0这块网卡上的”。在 Linux 系统中enp5s0是物理以太网卡的名称en代表以太网p5s0代表 PCI 总线位置。在 Windows 上你通常会看到%3或%4这样的数字索引。
返回列表