在这种模型下,任何发送方可以发送给任何组。在路由器角度上看,只要接收方“注册”了自己属于组播,任何发送方(任何源)的数据都会分到接收方。
接收方在“注册”自己加入组的同时,还会告诉路由器只接受某几个发送方(指定源),包括一个组地址和一个源IP地址。在这种模型下,其实任何发送方还是可以发送给任何组的。只是路由器会根据注册信息里的只把“合法源”的数据给到接收方。
从网络配置人员的角度看SSM避免了ASM部署的复杂性,从程序员角度看,SSM要比ASM麻烦一点点就是在加入组播的“注册”过程中,要把“源”的IP信息加进去。可能是我孤陋寡闻可能是Qt真的没考虑这个SSM的应用,至少从Qt4.8.X直到用的Qt5.9.X的Qt封装的QUdpSocket感觉没有支持的特别到位。
除了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
核心是用QUdpSocket的socketDescriptor()函数得到,最基础的socket号,让标准Socket的setsockopt设置“注册”过程,因为linux和windows的socket函数用的不一样,各实现以下就好了。
Dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include
#include
#include
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
Dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
#include
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
// 注意不可以在之前包含"windows.h",否则会导入"winsock.h",里面宏的定义不一致
//#include "windows.h"
#include
#include
//如果是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
#include
#include
#include
#include
#include
#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组播的坑都总结写在这个程序里了。有机会再讨论。[email protected]
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() <textEdit_2->toPlainText()<<":"<
main.cpp
#include "dialog.h"
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Dialog w;
w.show();
return a.exec();
}
dialog.ui就算了
上传了资源直接下载吧,https://download.csdn.net/download/houmingyang/87610587
qt5.9能编过。
本质上SSM这个事情不复杂,就是window下和linux下不太一样,甚至不同的linux版本里,对这协议的实现上感觉还是有细微的差距的。这个qt程序只是最简单的示意以下,很多具体问题要具体分析。其实都已经到直接些socket了我更倾向用原始的c/c++实现,有什么问题更容易发现和调整。qt封装的没问题,就是遇到稍微复杂具体问题的时候需要结合tcpdump的抓包具体分析调整。
bind的时候不论如何不要用ip地址,有的操作系统支持bind组播地址,大部分保险还是用0.0.0.0吧。bind主要是绑定的端口,不是ip。除非想用udp单播。
设置组播用哪块网卡不是用bind函数,而是用选好的ip去设置socket的interface。
对于发送的socket不bind也可以,如果bind了,就不是系统随机分配发送端口了,而是用bind的端口发送了。
关于端口要有个基本概念:每个TCPIP的包,其实包含两个端口信息,一个是发送的socket的源端口(发送的时候如果不指定,系统会随机分配一个),一个是接收的socket要bind的目的端口。