精华内容
下载资源
问答
  • IP多播

    2021-01-12 09:58:33
    使用广播服务,封包可以被发送到网络中的每个节点,而使用本节将介绍的多播服务,封包仅被发送到网络节点的一个集合。 IGMP 是IPv4 引入的管理多播客户和它们之间关系的协议。 一、多播地址 为了发送IP 多播数据,...

    使用广播服务,封包可以被发送到网络中的每个节点,而使用本节将介绍的多播服务,封包仅被发送到网络节点的一个集合。
    IGMP 是IPv4 引入的管理多播客户和它们之间关系的协议。

    一、多播地址

    为了发送IP 多播数据,发送者需要确定一个合适的多播地址,这个地址代表一个组。IP多播地址采用D 类IP 地址确定多播的组,地址的范围是224.0.0.0~239.255.255.255。不过,有许多多播地址保留为特殊目的使用,下表列出了一些比较重要的地址(保留的IP 多播地址):
    224.0.0.0    基地址(保留)
    224.0.0.1     本子网上的所有节点
    224.0.0.2     本子网上的所有路由器
    224.0.0.4     网段中所有的 DVMRP 路由器
    224.0.0.5     所有的 OSPF 路由器
    224.0.0.6    所有的 OSPF 指派路由器
    224.0.0.9     所有的 RIPv2 路由器
    224.0.0.13     所有的 PIM 路由器

    二、使用IP多播

    1. 加入和离开组

    加入和离开多播组可以使用setsockopt 函数,也可以使用WSAJoinLeaf 函数。使用setsockopt 函数更方便一点
    有两 个套接字选项控制组的加入和离开: IP_ADD_MEMBERSHIP 和 IP_DROP_MEMBERSHIP,套接字选项级别是 IPPROTO_IP,输入参数是一个 ip_mreq 结构,定义如下:
    typedef struct {
        struct in_addr imr_multiaddr; // 多播组的IP 地址
        struct in_addr imr_interface; // 将要加入或者离开多播组的本地地址
    } ip_mreq;
    下面的代码示例了如何加入组,其中s 是已经创建好的数据报套接字:

    ip_mreq mcast;
    mcast.imr_interface.S_un.S_addr = INADDR_ANY;
    mcast.imr_multiaddr.S_un.S_addr = inet_addr("234.5.6.7");
    int nRet = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mcast, sizeof(mcast));
    加入一个或者多个多播组之后,可以使用IP_DROP_MEMBERSHIP 选项离开特定的组:
    ip_mreq mcast;
    mcast.imr_interface.S_un.S_addr = dwInterFace;
    mcast.imr_multiaddr.S_un.S_addr = dwMultiAddr;
    int nRet = ::setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&mcast, sizeof(mcast));

    2. 接收多播数据

    主机在接收IP 多播数据之前,必须成为IP 多播组的成员。为了接收发送到特定端口的多播封包,有必要绑定到那个本地端口,而不显式地指定本地地址。如果绑定套接字设置了 SO_REUSEADDR 选项,就有不止一个进程可以绑定到UDP 端口,如下代码所示。
    BOOL bReuse = TRUE;
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&bReuse, sizeof(BOOL));
    如此一来,每个来到这个共享端口的多播或广播UDP 封包都会被发送给所有绑定到此端口的套接字。由于向前兼容的原因,这并不包括单播封包-单播封包永远不会被发送到多个套接字。
    下面是接收多播封包的程序,它绑定到本地端口 4567 之后,便加入多播组234.5.6.7,循环调用recvfrom 函数接收发送到多播组中的数据:

    void main()
    { 
    	// 创建UDP套接字
    	SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0);
    	// 允许其他进程使用绑定的地址(端口复用)
    	BOOL bReuse = TRUE;
    	::setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&bReuse, sizeof(BOOL));
    	// 绑定到4567 端口
    	sockaddr_in si;
    	si.sin_family = AF_INET;
    	si.sin_port = ::ntohs(4567);
    	si.sin_addr.S_un.S_addr = INADDR_ANY;
    	::bind(s, (sockaddr*)&si, sizeof(si));
    	// 加入多播组
    	ip_mreq mcast;
    	mcast.imr_interface.S_un.S_addr = INADDR_ANY;
    	mcast.imr_multiaddr.S_un.S_addr = ::inet_addr("234.5.6.7"); // 多播地址为234.5.6.7
    	::setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mcast, sizeof(mcast));
    	// 接收多播组数据
    	char buf[1280];
    	int nAddrLen = sizeof(si);
    	while(TRUE)
    	{ 
    		int nRet = ::recvfrom(s, buf, strlen(buf), 0, (sockaddr*)&si, &nAddrLen);
    		if(nRet != SOCKET_ERROR)
    		{ 
    			buf[nRet] = '\0';
    			printf(buf);
    		}
    		else
    		{ 
    			int n = ::WSAGetLastError();
    			break;
    		}
    	}
    }
    

    3. 发送多播数据

    要向组发送数据,没有必要非加入那个组。在前面的例子中,以234.5.6.7 为目的地址,4567 为目的端口调用sendto 函数,即可向多播组234.5.6.7 发送数据。默认情况下,发送的IP 多播数据报的TTL 等于1,这使得它们不能被发出子网。套接字选项 IP_MULTICAST_TTL 用来设置多播数据报的TTL 值(范围为0~255),如下代码所示。

    BOOL SetTTL(SOCKET s, int nTTL) // 自定义设置多播数据TTL 的函数
    { 
        int nRet = ::setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&nTTL, sizeof(nTTL));
        return nRet != SOCKET_ERROR;
    }

    为了提供有意义的范围控制,多播路由器支持TTL"极限"的概念,它阻止TTL小于特定值的数据报在特定子网上传输。极限执行如下约定:
    初始TTL为0         多播封包被限制在同一个主机
    初始TTL为1         多播封包被限制在同一个子网
    初始TTL为32     多播封包被限制在同一个站点
    初始TTL为64     多播封包被限制在同一个地区
    初始TTL为128     多播封包被限制在同一个大陆
    初始TTL为255     多播封包没有范围限制
    许多多播路由器拒绝转发目的地址在224.0.0.0~224.0.0.255 之间的任何多播数据报,不管它的TTL 是多少。这个地址范围是为路由协议和其他底层拓扑发现协议或者维护协议预留的,如网关发现和组成员报告等。每个多播传输仅从一个网络接口发出,即便是主机有多个多播接口。系统管理者在安装
    过程中就指定了多播使用的默认接口。可以使用套接字选项 IP_MULTICAST_IF 改变默认的发送数据接口,如下代码所示。
    struct in_addr addr;
    setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr));
    其中,addr 是本地机器上对外的接口。设置为地址 INADDR_ANY 可以恢复使用默认接口。选项 IP_MULTICAST_LOOP 可以设置多播回环是否打开。如果值为真,发送到多播地址的数据会回显到套接字的接收缓冲区。默认情况下,当发送IP 多播数据时,如果发送方也是多播组的一个成员,数据将回到发送套接字。如果设置此选项为FALSE,任何发送的数据都不会被发送回来。

    4.带源地址的IP 多播

    带源地址的IP 多播允许加入组时指定要接收哪些成员的数据。在这种情况下有两种方式加入组。第一种是"包含"方式,在这种方式下,为套接字指定N 个有效的源地址,套接字仅接收来自这些源地址的数据。另外一种是"排除"方式,在这种方式下,为套接字指定N个源地址,套接字将接收来自这些源地址之外的数据,也就是来自其他成员的数据。要使用"包含"方式加入多播组,应使用套接字选项 IP_ADD_SOURCE_MEMBERSHIP 和 IP_DROP_SOURCE_MEMBERSHIP。第一步是添加一个或者多个源地址。这两个套接字选项的输入参数都是一个ip_mreq_source 结构。

    struct ip_mreq_source {
    	struct in_addr imr_multiaddr; // 多播组的IP 地址
    	struct in_addr imr_sourceaddr; // 指定的源IP 地址
    	struct in_addr imr_interface; // 本地IP 地址接口
    };

    mr_sourceaddr 域指定了源IP 地址,套接字将接收来自此IP 地址的数据。如果有多个有效的源地址,IP_ADD_SOURCE_MEMBERSHIP 就应该被调用多次。下面的例子在本地接口上加入了多播组234.5.6.7,同时指定仅接收来自218.12.255.113 和218.12.174.222 的数据:

    SOCKET s = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    // 本地接口
    SOCKADDR_IN localif;
    localif.sin_family = AF_INET;
    localif.sin_port = htons(5150);
    localif.sin_addr.s_addr = htonl(INADDR_ANY);
    ::bind(s, (SOCKADDR *)&localif, sizeof(localif));
    // 设置ip_mreq_source 结构
    struct ip_mreq_source mreqsrc;
    mreqsrc.imr_interface.s_addr = inet_addr("192.168.0.46");
    mreqsrc.imr_multiaddr.s_addr = inet_addr("234.5.6.7");
    // 添加源地址218.12.255.113
    mreqsrc.imr_sourceaddr.s_addr = inet_addr("218.12.255.113");
    ::setsockopt(s, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, (char *)&mreqsrc, sizeof(mreqsrc));
    // 添加源地址218.12.174.222
    mreqsrc.imr_sourceaddr.s_addr = inet_addr("218.12.174.222");
    ::setsockopt(s, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, (char *)&mreqsrc, sizeof(mreqsrc));

    排除方式,为了加入多播组,同时排除一个或者多个源地址,加入组时使用 IP_ADD_MEMBERSHIP 选项。加入组后,便可以使用 IP_BLOCK_SOURCE 选项来指定要排除的源地址了。这里,输入参数也是 ip_mreq_source 结构。如果应用程序想从以前排除的地址接收数据,它可以通过使用IP_UNBLOCK_SOURCE选项从排除集合中移除此源地址,输入参数仍是ip_mreq_source 结构。

    展开全文
  • 多播路由技术

    2021-11-30 09:48:10
    什么是多播转发树? 用图论术语描述从特定源节点到多播组的所有成员的一组路径,这些路径定义了图论中的树(tree,是不含任何回路的图,即一个路由器不会在一条路径上出现两次或两次以上),有时也称为转发树。每个...

    什么是多播转发树?

    用图论术语描述从特定源节点到多播组的所有成员的一组路径,这些路径定义了图论中的树(tree,是不含任何回路的图,即一个路由器不会在一条路径上出现两次或两次以上),有时也称为转发树。每个多播路由器对应于树中的一个结点。连接两个路由器的网络则对应于树种的一条边。分组的源结点是树的根或根结点。从源结点出发,沿着一条路径到达的最后一个路由器称为叶子路由器。连接在叶子路由器上的网络称为叶子网络。

    基于源的树

    为了进行多播,需要这样一棵树,源主机是树根,而多播组的成员是树叶,这棵树连接了一个多播组的所有结点,并且从树根到树叶的每一条路径都是可能的最短路径,这样的树叫最短路径树(SPT)。由多播组的源S1(IP地址为192.168.0.66)发送给多播组G(组地址为239.0.0.11)的多播分组所经过的路径,用带箭头的实现表示。带箭头的实线形成已多播源为根的SPT转发树。一般使用一组对(S,G)来表示一颗SPT树,其中S表示多播源的IP地址,G表示要加入的多播组。图中的多播源S1的SPT树可以表示为(192.168.0.66,239.0.0.11)。这种表示方式意味着,对于组内的每个多播源都存在相应的SPT树不存在一个中心多播路由器

    共享树

    共享树也叫汇聚点树(RPT),汇聚点作为RPT树的公共根。与基于源的树SPT不同,发送多播分组的源主机不是直接向网络中发送多播分组,而是先把分组发往RPT树的根RP。然后再由RP向网络中的所有成员发送它收到的多播分组。先给到RP,如图中虚线;然后RP发给主机,如图中实线。RP相当于一个中心多播路由器,减少了路由器多播路由表中表项的数量。

    多播路由协议

    多播路由协议可以按照密集模式和稀疏模式两种方式工作。目前密集模式下的常见协议有距离向量多播路由协议DVMRP、多播开放最短路径优先协议MOSPF和密集模式协议无关多播PIM-DM稀疏模式下的协议有稀疏模式协议无关多播PIM-SM和基于核心树的多播协议CBT

    通常密集模式下的协议采用SPT树状类型,稀疏模型采用RPT进行路由

    多播路由协议分为哪些类型?多播路由协议DVMRP、MOSPF、PIM-DM、PIM-SM和CBT分别属于哪一种路由协议?

    答案如上两段。


    距离向量多播路由协议DVMRP

    是基于源的路由协议,第一个支持多播功能的路由协议,实现了具有逆向路径转发、嫁接和剪枝的基于源的多播树。

    逆向路径转发(RPF)拒绝转发示例

    单播路由表中的每个表项至少有目的网络和接口两个字段。图中路由器RB的路由表中的第一个路由表项表示的意义可以这样理解,路由器RB接收到的单播分组,如果其目的网络为192.168.0.0/16,则从接口E1转发出去,即接口E1为可能到源网络192.168.0.0/16的转发接口。如图,假设从串行接口S0接收到了来自多播源192.168.0.22的分组,路由器RB查看路由表时,发现此分组不是在从RB到多播源的最短路径上转发过来的。所以,根据RPF技术,路由器RB将拒绝转发该分组,丢弃之。

    剪枝和嫁接

    DVMRP使用广播方式把第一个分组发送到每一个网络,然后分组还要经理两个过程:剪枝和嫁接。

    • 洪泛和剪枝

    洪泛路由算法(下图虚线)是一个简单有效的路由算法,其基本思想是每个节点都是用广播转发收到的数据分组,若收到重复分组则进行丢弃处理。洪泛算法最大的问题是会产生大量的重复分组,占用网络资源,使路由器和链路的资源过于浪费,以致效率很低。

    为了减轻那些不存在接受者的分支上的相关资源的浪费,这些分支上的多播路由器会向其上游路由器发送剪枝报文(实线),阻止不需要的多播分组的继续转发。这样剪枝的结果便是没有多播接受者的分支被剪枝,最初洪泛形成的转发树只剩下有接收者的分支。

    带箭头的虚线便是最初洪泛形成的转发树,然后将发送剪枝报文的删掉,如下图:

    得到剪枝后形成的转发树如下:

    • 嫁接

    嫁接是把被剪的转发树分支重新加入到多播转发树上。

    假定与路由器RD相连的网络中的主机PB想接收多播源S发送来的多播分组,由于此时RD已经在剪枝过程中被剪掉,目前它不在多播转发树中。所以RD收到加入多播组的报文后,会向其上游路由器RB发送嫁接报文,路由器RB收到RD的嫁接报文后,把与RD相连的接口置为转发状态,RD分支又回到多播转发树中。


    CBT构造过程示例

    CBT是共享树,及多播中的所有源结点共享这棵树,使用一个核心作为树根。

    如图,R3、R8和R6路由器连接的网络有多播组成员,R4和R7所连接的路由器没有多播组的成员,路由器R7所连接的网络中有主机想加入多播组时,主机发送一个IGMP成员关系报告报文,路由器R7收到这个报告报文,就启动了构造一棵CBT的过程,如下:

    • 定位核心路由器

    • 当一个新成员主机想加入组G时,一个与该主机直接连接的CBT路由器通过最短路径向核心结点发送加入join报文。

    • 如果接收到join报文的某个中间路由器已经是CBT的成员,它将通过join报文的逆向路径向新成员发回加入应发答join-ack报文。joinp-ack路径上的每个路由器都将执行下面的操作:创建一个新的包括多播组地址、入接口和出接口的状态项。


    PIM-DM和PIM-SM

    PIM-DM只支持源树,使用推的方式,把多播流向网络的各个地方转发,从而把流推给不同接收者。这种方式适用于网络中的各个子网都有接收者的情况。PIM-DM一开始向网络中的各处发多播流,路由器每隔3分钟检查一次自己是否还存在“下游”的邻居,如果没有,就把这个流剪掉。

    PIM-SM使用拉的方式,只有存在接收者的网段才会接收到数据流。PIM-SM通过在共享树中转发数据包来散布组播源的信息。PIM-SM使用共享树,它需要指定一个汇聚点(RP),一旦其它路由器收到从共享树来的数据后,就知道了数据的源在哪里。于是,路由器就会向源发送PIM(S,G)加入信息。在反向路径上的每个路由器比较自己的单播路由表中它到RP的度量和它到源的度量,如果到源的度量更优,它就会继续发送PIM(S,G)加入信息。否则,PIM(S,G)信息就会沿着RP的方向来发送。这样,就生成了共享树和源树。单向共享树,靠近源的路由器先向RP注册,然后在源和RP之间生成源树,数据通过共享树(*,G)到达接收者。

    如何建立源到RP的基于源的树?

    PIM-SM示例

    什么时候需要从共享树切换到基于源的树(源到多播组成员)?如何切换?

    PIM-SM的工作过程就是SPT和RPT建立的过程。RPT可以切换成SPT,一般会在用户端设置一个阀值,当组播流超过阀值的时候,就会切换成SPT。默认阀值为0,也就是一有组播流就从RPT切换成SPT。


    共享树的构建

    以R3为RP的共享树,举例如下:


    多播源发现协议MSDP

    用来发现其他PIM-SM域内的多播源信息,配置了MSDP对等体的RP,将其域内的活动多播源信息通过SA报文(携带多组(S,G)信息,在多个RP之间传递)通告给它的所有MSDP对等体,这样一个PIM-SM域内的多播源信息就会被传递到另一个PIM-SM域。如下图:

    展开全文
  • 1. 概述 1.1 单播用于两个主机间单对单的通信 1.2广播用于一个主机对整个局域网上所有主机上的数据通信 ...1.6多播是 IPv6 数据包的 3 种基本目的地址类型之一,多播是一点对多点的通信, IPv6 没有采用 IPv

    1. 概述
    1.1 单播用于两个主机间单对单的通信
    1.2广播用于一个主机对整个局域网上所有主机上的数据通信
    1.3单播和广播是两个极端,要么对一个主机进行通信,要么对整个局域网的主机进行通信
    1.4实际情况下,经常需要对一组特定的主机进行通信,而不是所有局域网上的主机,这时候就有了组播
    1.5IP组播(也称多址广播或多播),是一种允许一台或多台主机发送数据包到多台主机的TCP/IP网路技术。
    1.6多播是 IPv6 数据包的 3 种基本目的地址类型之一,多播是一点对多点的通信, IPv6 没有采用 IPv4 中的组播术语,而是将广播看成是多播的一个特殊例子。

    2.组播的特点
    组播也可以称之为多播这也是 UDP 的特性之一。组播是主机间一对多的通讯模式,是一种允许一个或多个组播源发送同一报文到多个接收者的技术。组播源将一份报文发送到特定的组播地址,组播地址不同于单播地址,它并不属于特定某个主机,而是属于一组主机。一个组播地址表示一个群组,需要接收组播报文的接收者都加入这个群组。

    广播只能在局域网访问内使用,组播既可以在局域网中使用,也可以用于广域网
    在发送广播消息的时候,连接到局域网的客户端不管想不想都会接收到广播数据,组播可以控制发送端的消息能够被哪些接收端接收,更灵活和人性化。
    广播使用的是广播地址,组播需要使用组播地址。
    广播和组播属性默认都是关闭的,如果使用需要通过 setsockopt () 函数进行设置。
    组播需要使用组播地址,在 IPv4 中它的范围从 224.0.0.0 到 239.255.255.255,并被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类

    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.255.255.255: 为本地管理组播地址,仅在特定的本地范围内有效

    组播地址不属于任何服务器或个人,它有点类似一个微信群号,任何成员(组播源)往微信群(组播 IP)发送消息(组播数据),这个群里的成员(组播接收者)都会接收到此消息。

    3.组播应用

    3.1 点对多点应用
    点对多点应用是指一个发送者,多个接收者的应用形式,这是最常见的多播应用形式。典型的应用包括:媒体广播、媒体推送、信息缓存、事件通知和状态监视等。

    3.2 多点对点应用
    多点对点应用是指多个发送者,一个接收者的应用形式。通常是双向请求响应应用,任何一端(多点或点)都有可能发起请求。典型应用包括:资源查找、数据收集、网络竞拍、信息询问等。

    3.3 多点对多点应用
    多点对多点应用是指多个发送者和多个接收者的应用形式。通常,每个接收者可以接收多个发送者发送的数据,同时,每个发送者可以把数据发送给多个接收者。典型应用包括:多点会议、资源同步、并行处理、协同处理、远程学习、讨论组、分布式交互模拟(DIS)、多人游戏等

    4. 设置组播属性
    如果使用组播进行数据的传输,不管是消息发送端还是接收端,都需要进行相关的属性设置,设置函数使用的是同一个,即:setsockopt(),函数原型如下:

    int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
    

    4.1 发送端
    发送组播消息的一端需要设置组播属性,具体的设置方式如下:

    /*
    struct in_addr
    {
        in_addr_t s_addr;	// unsigned int
    };
    */
    
    /*
    函数:setsockopt
    描述:配置发送端UDP组播属性
    参数:
    	sockfd:	用于 UDP 通信的套接字
    	level:		套接字级别,设置组播属性需要将该参数指定为:IPPTOTO_IP
    	optname: 	套接字选项名,设置组播属性需要将该参数指定为:IP_MULTICAST_IF
    	optval:	设置组播属性,这个指针需要指向一个 struct in_addr{} 类型的结构体地址,这个结构体地址用于存储组播地址,并且组播 IP 地址的存储方式是大端的。
    	optlen:optval 指针指向的内存大小,即:sizeof(struct in_addr)
    	
    返回值:函数调用成功返回 0,调用失败返回 - 1	
    */
    
       struct in_addr opt;
       // 将组播地址初始化到这个结构体成员中即可
       inet_pton(AF_INET, GROUP_IP, &opt.s_addr);
       setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &opt, sizeof(opt));
    

    4.2 接收端
    因为一个组播地址表示一个群组,所以需要接收组播报文的接收者都加入这个群组,和想要接收群消息就必须要先入群是一个道理。加入到这个组播群组的方式如下:

    /*
    struct in_addr
    {
        in_addr_t s_addr;	// unsigned int
    };
    
    struct ip_mreqn
    {
        struct in_addr imr_multiaddr;   // 组播地址/多播地址
        struct in_addr imr_address;     // 本地地址
        int   imr_ifindex;              // 网卡的编号, 每个网卡都有一个编号
    };
    // 必须通过网卡名字才能得到网卡的编号: 可以通过 ifconfig 命令查看网卡名字
    #include <net/if.h>
    // 将网卡名转换为网卡的编号, 参数是网卡的名字, 比如: "ens33"
    // 返回值就是网卡的编号
    unsigned int if_nametoindex(const char *ifname);
    */
    
    /*
    函数:setsockopt
    描述:配置接收端UDP组播属性
    参数:
    	sockfd:基于 udp 的通信的套接字
    	level:套接字级别,加入到多播组该参数需要指定为:IPPTOTO_IP
    	optname:套接字选项名,加入到多播组该参数需要指定为:IP_ADD_MEMBERSHIP
    	optval:加入到多播组,这个指针应该指向一个 struct ip_mreqn{} 类型的结构体地址
    	optlen:optval 指向的内存大小,即:sizeof(struct ip_mreqn)
    */
        struct ip_mreqn opt;
        // 要加入到哪个多播组, 通过组播地址来区分
        inet_pton(AF_INET, GROUP_IP, &opt.imr_multiaddr.s_addr);
        opt.imr_address.s_addr = htonl(INADDR_ANY);
        opt.imr_ifindex = if_nametoindex("ens33");
        setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &opt, sizeof(opt));
    

    5. 组播通信流程
    发送组播消息的一端需要将数据发送到组播地址和固定的端口上,想要接收组播消息的终端需要绑定对应的固定端口然后加入到组播的群组,最终就可以实现数据的共享。

    在这里插入图片描述

    6.代码举例

    6.1 发送端代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <arpa/inet.h>
    
    #define GROUP_IP "224.0.1.0"
    //#define GROUP_IP "239.0.1.10"
    
    int main()
    {
        // 1. 创建通信的套接字
        int fd = socket(AF_INET, SOCK_DGRAM, 0);
        if(fd == -1)
        {
            perror("socket");
            exit(0);
        }
    
        // 2. 设置组播属性 (经测试可以不设置发送端组播属性也能正常发送)
        struct in_addr opt;
        // 将组播地址初始化到这个结构体成员中即可
        inet_pton(AF_INET, GROUP_IP, &opt.s_addr);
        setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &opt, sizeof(opt));
    
        char buf[1024];
    	char sendaddrbuf[64];
    	
    	socklen_t len = sizeof(struct sockaddr_in);
    	struct sockaddr_in sendaddr;
    	
        struct sockaddr_in cliaddr;
        cliaddr.sin_family = AF_INET;
        cliaddr.sin_port = htons(9999); // 接收端需要绑定9999端口
        // 发送组播消息, 需要使用组播地址, 和设置组播属性使用的组播地址一致就可以
        inet_pton(AF_INET, GROUP_IP, &cliaddr.sin_addr.s_addr);
    						
        // 3. 通信
        int num = 0;
        while(1)
        {
    		memset(buf, 0, sizeof(buf));
            sprintf(buf, "hello, client...%d\n", num++);
            // 数据广播
            sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&cliaddr, len);
            printf("发送的组播的数据: %s\n", buf);
    		memset(buf, 0, sizeof(buf));
    		recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sendaddr, &len);
    		printf("sendaddr:%s, prot:%d\n", inet_ntop(AF_INET, &sendaddr.sin_addr.s_addr,  sendaddrbuf, sizeof(sendaddrbuf)),  sendaddr.sin_port);
    		printf("接收到的组播消息: %s\n", buf);
        }
        close(fd);
        return 0;
    }
    

    6.2 接收端代码

    #define GROUP_IP "224.0.1.0"
    //#define GROUP_IP "239.0.1.10"
    
    
    int main()
    {
        // 1. 创建通信的套接字
        int fd = socket(AF_INET, SOCK_DGRAM, 0);
        if(fd == -1)
        {
            perror("socket");
            exit(0);
        }
    
        // 2. 通信的套接字和本地的IP与端口绑定
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(9999);    // 大端
        addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 0.0.0.0
        int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
        if(ret == -1)
        {
            perror("bind");
            exit(0);
        }
    
        // 3. 加入到多播组
    	#if 0 //使用struct ip_mreqn或者 struct ip_mreq 设置接收端组播属性都可以正常接收
        struct ip_mreqn opt;
        // 要加入到哪个多播组, 通过组播地址来区分
        inet_pton(AF_INET, GROUP_IP, &opt.imr_multiaddr.s_addr);
        opt.imr_address.s_addr = htonl(INADDR_ANY);
        opt.imr_ifindex = if_nametoindex("ens33");
        setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &opt, sizeof(opt));
    	#else
    	struct ip_mreq mreq; // 多播地址结构体
    	mreq.imr_multiaddr.s_addr=inet_addr(GROUP_IP);
    	mreq.imr_interface.s_addr = htonl(INADDR_ANY);	
    	ret=setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
    	#endif
    	
        char buf[1024];
    	char sendaddrbuf[64];
    	socklen_t len = sizeof(struct sockaddr_in);
    	struct sockaddr_in sendaddr;
    	
        // 3. 通信
        while(1)
        {
            // 接收广播消息
            memset(buf, 0, sizeof(buf));
            // 阻塞等待数据达到
            recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sendaddr, &len);
    		printf("sendaddr:%s, prot:%d\n", inet_ntop(AF_INET, &sendaddr.sin_addr.s_addr,  sendaddrbuf, sizeof(sendaddrbuf)),  sendaddr.sin_port);
            printf("接收到的组播消息: %s\n", buf);
    		sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr *)&sendaddr, len);
        }
        close(fd);
        return 0;
    }
    

    参考于: 苏丙榅:https://subingwen.cn/linux/multicast/#4-1-%E5%8F%91%E9%80%81%E7%AB%AF

    参考于:https://blog.csdn.net/weixin_43790540/article/details/104244546

    展开全文
  • 多播(multicast)又称为组播,是一种介于单播(一对一)和广播(一对全部)之间的一种数据发送方式,只有位于一个多播组内的实体能够接收到发送到该多播组的数据包。 多播地址范围 多播地址总的范围为224.0.0.0~239...

    多播

    多播(multicast)又称为组播,是一种介于单播(一对一)和广播(一对全部)之间的一种数据发送方式,只有位于一个多播组内的实体能够接收到发送到该多播组的数据包。

    多播地址范围

    多播地址总的范围为224.0.0.0~239.255.255.255,每一个地址表示一个多播组,简单的细分范围如下:

    地址范围说明
    224.0.0.0~224.0.0.255仅本地同一个子网使用,不可路由
    224.0.1.0~224.0.1.255公网可以使用的多播地址,可以在公网路由
    224.0.2.0~239.255.255.255内部网络可用,可路由

    更完整的地址细分参考 Multicast address - Wikipedia

    关键数据结构

    ip_mreq和ip_mreqn

    ip_mreqip_mreqn是用于设置网卡加入多播组数据结构,两个数据结构基本功能相同,可以替换使用,区别在于ip_mreqn是Linux2.2之后加入的新数据结构,比ip_mreq多了一个字段,具体如下:

    /* Internet address.  */
    typedef uint32_t in_addr_t;
    struct in_addr
    {
        in_addr_t s_addr;
    };
    
    // ip_mreq是一个旧的数据结构,但目前仍然可用
    struct ip_mreq
    {
        /* IP multicast address of group.  */
        struct in_addr imr_multiaddr;
    
        // 设置加入多播组的的网卡ip, 注意这里并不表示socket同该网卡绑定
        // 该socket仍然能够接收到不是该网卡的数据包,该设置仅仅表示该ip
        // 对应的网卡能够接收对应多播组的数据包
        struct in_addr imr_interface;
    };
    
    // ip_mreqn是从Linux 2.2之后可用的新的数据结构,相比ip_mreq,其多了一个imr_ifindexy
    struct ip_mreqn {
        struct in_addr imr_multiaddr;
        // 设置加入多播组的的网卡ip, 注意这里并不表示socket同该网卡绑定
        // 该socket仍然能够接收到不是该网卡的数据包,该设置仅仅表示该ip
        // 对应的网卡能够接收对应多播组的数据包
        struct in_addr imr_address;   
        // 设置加入多播组的网卡的index,该设置项优先级高于上边的网卡ip
        int            imr_ifindex; 
    };
    

    ip_mreqn结构记录了需要加入的的多播组的网卡ip(网卡index)

    使用如下函数可以将网卡名称(ifconfig查看)转换为对应的index,用于填充imr_ifindex域,也可以将imr_ifindex设置为0表示使用默认网卡

    #include <net/if.h>
    unsigned int if_nametoindex (const char *__ifname)
    

    发送多播数据包

    仅发送多播数据包不需要加入多播组,将目的地址设置为对应的多播地址即可,在Linux中,如果不设置发送对应多播地址使用的网卡,则会使用系统会使用路由表自动选择默认网卡进行发送(如果存在默认路由)。但通常多播数据包可能不是需要发送到默认路由,因此我们需要指定发送多播数据包使用的网卡,有以下三种方式:

    1. 使用setsockopt通过IP_MULTICAST_IF选项设置发送数据包到特定多播地址时使用的对应网络接口地址。
    struct ip_mreq mreq;
    // 设置多播地址
    mreq.imr_multiaddr.s_addr = inet_addr("226.1.11.111");
    // 设置使用该多播地址发送数据时使用的网卡ip
    mreq.imr_interface.s_addr = inet_addr("192.168.1.2");
    // 调用setsockopt
    ret = setsockopt(socks[i], IPPROTO_IP, IP_MULTICAST_IF, &mreq,sizeof(mreq));
    if ( ret < 0) {
        LOG_ERROR("Fail to join udp group, err: %s", strerror(errno));
        goto out;
    }
    
    1. 使用setsockopt设置SO_BINDTODEVICE将该socket绑定到对应的网卡,此后通过该socket接收和发送的数据均只能通过绑定的网卡(需要root权限)。
    char* interface = "eth0";
    ret = setsockopt(socks[i], SOL_SOCKET,SO_BINDTODEVICE, interface, strlen(interface));
    if ( ret < 0 ) {
        LOG_ERROR("Fail to bind to interface, err: %s", strerror(errno));
        goto out;
    }
    
    1. 在路由表中指定对应多播地址使用的网卡地址(即将多播地址当作一个普通网段的地址,为该网段增加一个路由表项)。
    route add -net 226.1.11.0 netmask 255.255.255.0 dev eth0
    

    发送多播数据包时,通常不希望收到自己发送出去的包(当自己也位于该多播组时),因此需要设置IP_MULTICAST_LOOP选项。

    // 禁止组播回送(防止收到自己发送的组播包)
    op = 0;
    ret = setsockopt(socks[i], IPPROTO_IP , IP_MULTICAST_LOOP, &op, sizeof(op));
    if (ret < 0) {
        LOG_ERROR("Fail to disable multicast loop, err: %s",strerror(errno));
        goto out;
    }
    

    接收多播数据包

    接收多播数据包需要使用IP_ADD_MEMBERSHIP加入一个多播组。

    // 加入组播
    struct ip_mreq mreq;
    // 设置需要接收的多播地址
    mreq.imr_multiaddr.s_addr = inet_addr("226.1.11.111");
    // 设置接收该多播地址数据的网卡ip
    mreq.imr_interface.s_addr = inet_addr("192.168.1.2");
    // 调用setsockopt
    ret = setsockopt(socks[i], IPPROTO_IP, IP_ADD_MEMBERSHIP , &mreq,sizeof (mreq));
    if ( ret < 0) {
        LOG_ERROR("Fail to join udp group, err: %s", strerror(errno));
        goto out;
    }
    

    如果需要一个socket仅接收特定网卡的多播数据包,在Linux中目前我找到的最好的办法就是设置SO_BINDTODEVICE选项将该socket绑定到指定网卡,如上一小节所述。

    关于socket的各种设置项目,参考ip(7) - Linux manual page (man7.org)

    关于Windows和Linux中socket绑定的区别

    在windows中,使用bind函数可以将socket绑定到对应ip的网卡上,而在Linux中,bind函数更像一个ip地址过滤的功能,并不会将socket同网卡进行绑定,Linux中必须使用setsocketopt来设置SO_BINDTODEVICE选项进行socket和网卡的绑定。

    上述结论是在多播发送和接收实验过程中得到的。windows中socket绑定网卡对应的ip后可以正常接收发送到该网卡的多播数据包,并且通过该socket发送的多播数据包也是通过绑定的网卡进行发送的。Linux中socket绑定网卡对应的ip后无法接收到发送到该网卡的多播数据包,必须绑定0.0.0.0或者多播地址才能接收到对应的多播数据包。

    附录

    socket相关的api

    #include <sys/socket.h>
    

    ip地址相关api

    #include <netinet/in.h>
    
    展开全文
  • 224.100.200.1 多播组 224.100.200.52多播组 思路: PC作为客户机,使用网络调试助手; LS开发板作为服务器端,部署对应程序(代码实现如下); 在UDP基本简单通信的基础上增加两组组播(setsockopt实现,一组...
  • 单播只能发送给一个接收...多播(组播):只有加入某个多播组的主机才能接收到数据。多播既可以发给多个主机,又能避免像广播那样带来的过多的负荷。组播的地址为D类地址:224.0.1.1-239.255.255.255那么代码实现如下...
  • IP多播详解。
  • 文章目录7.1 多播地址定义7.2 多播地址的结构7.3 多播地址的范围7.3.1 以图形化方式表示的范围:7.3.2 已分配的多播地址7.4 请求节点多播地址7.4.1 定义7.4.2 地址解析7.4.3 DAD7.4.4 请求节点多播地址的结构 7.1 ...
  • IPv6地址、单播、多播/组播介绍

    千次阅读 2021-04-17 17:25:52
    IPv6地址、单播、多播/组播介绍 1. IPv6地址介绍 这里主要介绍IPv6地址,什么是IP地址就不介绍了。 IPv6地址主要是为了解决IPv4地址枯竭的问题。 1.1 IPv6地址的格式 IPv6地址有128位,通常使用16进制表示,一个16...
  • 计算机网络——基于IP多播的网络会议程序一、实验目的二、总体设计1. 基本原理2. 设计步骤(1)receiver(2)sender三、详细设计1. 程序流程图2. 实验代码(1)receiver(2)sender四、实验结果 一、实验目的 参照...
  • 一、单(一对一通信) 主机之间一对一的通讯模式,网络中的交换机和路由器对数据只进行转发不进行复制。如果10个客户机需要相同的数据,则服务器需要逐一传送,重复10次相同的工作。但由于其能够针对每个客户的...
  • 【计算机网络】单播、多播(组播)和广播 单播、多播(组播)和广播的区别? 单播:网络节点之间的通信就好像是人们之间的对话一样。如果一个人对另外一个人说话,那么用网络技术的术语来描述就是“单播”,此时...
  • 捕获广播或多播地址数据MAC地址数据端口应用程序数据Wireshark网络分析实例集锦大学霸Wireshark网络分析实例集锦大学霸3.8.3 捕获广播或多播地址数据广播地址就是当IP地址的网络和主机位全为1时,就是广播地址255....
  • 只是另一个解释其他答案代码中一些微妙点的答案:socket.INADDR_ANY-(编辑)在IP_ADD_MEMBERSHIP,这并不真正将套接字绑定到所有接口,而只是选择启动多播的默认接口(根据路由表)多播接收器:import socketimport struct...
  • W5500+UDP 多播\app.depW5500+UDP 多播\app.ewdW5500+UDP 多播\app.ewpW5500+UDP 多播\app.ewwW5500+UDP 多播\app.icfW5500+UDP 多播\Debug\Exe\app.binW5500+UDP 多播\Debug\Exe\app.outW5500+UDP 多播\Debug\List\...
  • 高恪固件多播

    2021-05-17 16:07:39
    一.关闭硬件WAN口功能 2.多线策略 设置负载均衡 三.单线多播 设置完即可插线连接 四.查看状态 校园网之前只能跑百兆,两个号叠加跑到200M
  • 用UDP通信实现组播通信(多播) 组播通信特点:发送端创建的聊天组,你想加入就加入,不想加入就不接收(想加入才把数据发给组播地址) Ip地址分类A, B, C, D(组播地址) ——224.0.0.0 — 239.255.255.255 原理图: ...
  • 多播委托 多播委托拥有大部分与单播委托相同的功能。它们只拥有对对象的弱引用,可以与结构体一起使用,可以四处轻松复制等等。 就像常规委托一样,多播委托可以远程加载/保存和触发;但多播委托函数不能使用返回值...
  • 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263import java.net.DatagramPacket;import java.net.InetAddress;import java.net.Multicas...
  • IP组播(multicast),也叫做多播、任播。组播传输技术必不可少,在日常生活比如开黑、视频聊天、多人协作文件都起着重要的作用。 在了解组播之前,复习一下什么是单播、广播: 单播(unicast):一对一 广播...
  • 用的A、B两台机器都是实体机,不是虚拟机,发现无论是在A还是在B上发送多播信息,多播组中的信息都无法被对方接收。 解决方法: 把A电脑上的多余网卡全部禁用,只保留通信用的那张网卡。 把B电脑上的多余...
  • 在这4种网络里,OSPF的5种报文所采取的单播或者多播的发送方式是不同的,分别如下:BMA网络:一般情况下,以组播形式发送Hello报文、LSU报文和LSAck报文,以单播形式发送DD报文和LSR报文。2. NBMA网络:所有报文都以...
  • ip多播需要两种协议 网际组管理协议IGMP 多播路由选择协议 网际组管理协议IGMP 让连接在本地局域网上的多播路由器直到本局域网上是否有主机(主机上的某个进程)参加或推出了某个多播多播组员通过IGMP协议向...
  • 多播技术基础

    2021-11-25 10:45:27
    单播、多播和广播 单播方式下,每一个结点一次只能给另一个结点发送数据报文 多播方式下,每个结点可以一次有效地把数据报文发送给网络的一组结点 广播方式下,一个结点可以一次将数据报文发送给网络上(通常是...
  • 在linux下用vlc在若干笔记本组成的无线网络中做视频传输实验如果用单播(unicast)的udp,效果很好,很流畅但如过用多播(Multicast)的udp,效果就很差,接收的图像上有很多方块,根本看不清图像换成rtp也没有好转谁...
  • 计算机网络中的广播和多播指的是什么?发布时间:2020-06-10 17:22:25来源:计算机网络中的广播和多播指的是什么?计算机网络中的广播是一种团体通信,其中发送方同时向接收方发送数据。这是一种全部通信模型,其中每...
  • 为什么要使用多播: 网卡从网络上接收到目标物理地址对应的所有bit位都为1的数据报时,会收到这条消息并将其上传给驱动程序,网卡的这种工作模式称为广播模式,网卡的缺省工作模式包含直接模式和广播模式。...
  • 网络编程——多播与广播

    千次阅读 2021-11-11 12:49:22
    与UDP服务器端/客户端的实现方式区别在于,UDP数据传输以单一目标进行,而多播数据同时传递传递到加入(注册)特定组的大量主机 MBone(Multicast Backbone,多播主干网) 多播是基于MBone这个虚拟网络工作的。虚拟...
  • 【网络硬件】7.IP多播的概念

    千次阅读 2021-11-27 19:39:46
    多播就是多个接收者同时发送相同的数据,一般用于同时传输动态画面等情况。 构成多播网络的路由器可以将特定主机发送的多播分组复制并发送到其他多个网络节点。 多播分组发送源主机称为source或者sender,接受端...
  • 我们的软件使用两个多播组与两个不同的物理网络上的两个不同的设备组进行通信.除此应用程序外,一个网络上的设备无需通过我们的设备进行通信,即可与另一个网络上的设备进行通信.为此,该软件创建了两个套接字.每个绑定...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 631,101
精华内容 252,440
关键字:

多播