加入一个组播组

 网络中的一台主机如果希望能够接收到来自网络中其它主机发往某一个组播组的数据报,那么这么主机必须先加入该组播组,然后就可以从组地址接收数据包。在广域网中,还涉及到路由器支持组播路由等,但本文希望以一个最为简单的例子解释清楚协议栈关于组播的一个最为简单明了的工作过程,甚至,我们不希望涉及到IGMP包。
    我们先从一个组播客户端的应用程序入手来解析组播的工作过程:
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <string.h>
    #include "my_inet.h"
    #include <arpa/inet.h>

    #define MAXBUF 256
    #define PUERTO 5000
    #define GRUPO "224.0.1.1"

    int main(void)
    {
        int fd, n, r;
        struct sockaddr_in srv, cli;
        struct ip_mreq mreq;
        char buf[MAXBUF];

        memset( &srv, 0, sizeof(struct sockaddr_in) );
        memset( &cli, 0, sizeof(struct sockaddr_in) );
        memset( &mreq, 0, sizeof(struct ip_mreq) );

        srv.sin_family = MY_AF_INET;
        srv.sin_port = htons(PUERTO);
        if( inet_aton(GRUPO, &srv.sin_addr ) < 0 ) {
                perror("inet_aton");
                return -1;
        }
        if( (fd = socket( MY_AF_INET, SOCK_DGRAM, MY_IPPROTO_UDP) ) < 0 ){
            perror("socket");
            return -1;
        }
        if( bind(fd, (struct sockaddr *)&srv, sizeof(srv)) < 0 ){
            perror("bind");
            return -1;
        }
        if (inet_aton(GRUPO, &mreq.imr_multiaddr) < 0) {
            perror("inet_aton");
            return -1;
        }
        inet_aton( "172.16.48.2", &(mreq.imr_interface) );
        if( setsockopt(fd, SOL_IP, IP_ADD_MEMBERSHIP, &mreq,sizeof(mreq)) < 0 ){
            perror("setsockopt");
            return -1;
        }
        n = sizeof(cli);
        while(1){
            if( (r = recvfrom(fd, buf, MAXBUF, 0, (struct sockaddr *)&cli, (socklen_t*)&n)) < 0 ){
                perror("recvfrom");
            }else{
                buf[r] = 0;
                fprintf(stdout, "Mensaje desde %s: %s", inet_ntoa(cli.sin_addr), buf);
            }
        }
    }

    这是一个非常简单的组播客户端,它指定从组播组224.0.1.1的5000端口读数据,并显示在终端上,下面我们通过分析该程序来了解内核的工作过程。
    前面我们讲过,bind操作首先检查用户指定的端口是否可用,然后为socket的一些成员设置正确的值,并添加到哈希表myudp_hash中。然后,协议栈每次收到UDP数据,就会检查该数据报的源和目的地址,还有源和目的端口,在myudp_hash中找到匹配的socket,把该数据报放入该socket的接收队列,以备用户读取。在这个程序中,bind操作把socket绑定到地址224.0.0.1:5000上, 该操作产生的直接结果就是,对于socket本身,下列值受影响:
    struct inet_sock{
        .rcv_saddr = 224.0.0.1;
        .saddr = 0.0.0.0;
        .sport = 5000;
        .daddr = 0.0.0.0;
        .dport = 0;
    }
    这五个数据表示,该套接字在发送数据包时,本地使用端口5000,本地可以使用任意一个网络设备接口,发往的目的地址不指定。在接收数据时,只接收发往IP地址224.0.0.1的端口为5000的数据。
    程序中,紧接着bind有一个setsockopt操作,它的作用是将socket加入一个组播组,因为socket要接收组播地址224.0.0.1的数据,它就必须加入该组播组。结构体struct ip_mreq mreq是该操作的参数,下面是其定义:
    struct ip_mreq
    {
        struct in_addr imr_multiaddr;   // 组播组的IP地址。
        struct in_addr imr_interface;   // 本地某一网络设备接口的IP地址。
    };
    一台主机上可能有多块网卡,接入多个不同的子网,imr_interface参数就是指定一个特定的设备接口,告诉协议栈只想在这个设备所在的子网中加入某个组播组。有了这两个参数,协议栈就能知道:在哪个网络设备接口上加入哪个组播组。为了简单起见,我们的程序中直接写明了IP地址:在172.16.48.2所在的设备接口上加入组播组224.0.1.1。
    这个操作是在网络层上的一个选项,所以级别是SOL_IP,IP_ADD_MEMBERSHIP选项把用户传入的参数拷贝成了struct ip_mreqn结构体:
    struct ip_mreqn
    {
        struct in_addr  imr_multiaddr;
        struct in_addr  imr_address;
        int             imr_ifindex;
    };
    多了一个输入接口的索引,暂时被拷贝成零。
    该操作最终引发内核函数myip_mc_join_group执行加入组播组的操作。首先检查imr_multiaddr是否为合法的组播地址,然后根据imr_interface的值找到对应的struct in_device结构。接下来就要为socket加入到组播组了,在inet_sock的结构体中有一个成员mc_list,它是一个结构体struct ip_mc_socklist的链表,每一个节点代表socket当前正加入的一个组播组,该链表是有上限限制的,缺省值为IP_MAX_MEMBERSHIPS(20),也就是说一个socket最多允许同时加入20个组播组。下面是struct ip_mc_socklist的定义:
    struct ip_mc_socklist
    {
        struct ip_mc_socklist   *next;
        struct ip_mreqn         multi;
        unsigned int            sfmode;     /* MCAST_{INCLUDE,EXCLUDE} */
        struct ip_sf_socklist   *sflist;
    };
    struct ip_sf_socklist
    {
        unsigned int    sl_max;
        unsigned int    sl_count;
        __u32           sl_addr[0];
    };
    除了multi成员,它还有一个源过滤机制。如果我们新添加的struct ip_mreqn已经存在于这个链表中(表示socket早就加入这个组播组了),那么不做任何事情,否则,创建一个新的struct ip_mc_socklist:
    struct ip_mc_socklist
    {
        .next = inet->mc_list;      //新节点放到链表头。
        .multi = 传入的参数;        //这是关键的组信息。
        .sfmode = MCAST_EXCLUDE;    //过滤掉sflist中的所有源。
        .sflist = NULL;             //没有源需要过滤。
    };
    最后,调用myip_mc_inc_group函数在struct in_device和struct net_device的mc_list链表中都添上相应的组播组节点,关于这部分的细节可以在前一篇文章《初识组播2》中找到。不再重复。
    到此为止,我们完成了最为简单的加入组播组的操作,对于同一子网内的情况,socket已经可以接收组播数据了,关于组播数据如何接收,下回分解。

http://hi.baidu.com/linux_kernel/blog/item/490ed8332a3ad743ac4b5f3c.html

<think>我们被要求回答:主机加入的方式是动态还是静态?以及主机如何加入一个组。 根据引用内容,特别是引用[1]和引用[5],我们可以知道: - 的管理和维护需要一套机制让网络设备知道子网中有哪些主机属于特定的。 - IGMP协议是用于路由器和主机之间建立IP与成员关系的协议。 引用[5]详细描述了IGMP协议的工作过程: 路由器向所有端口发送查询包(query),主机收到查询包后,可以发送报告包(report)来声明加入某个。 因此,主机加入通常是通过IGMP协议动态加入的。但是,也存在静态配置的方式。 所以,主机加入有两种主要方式: 1. 动态加入:通过IGMP协议,主机在收到路由器的查询后,主动发送报告加入某个。这是最常见的方式。 2. 静态加入:在主机操作系统或网络设备上配置,使主机永久加入某个,而不依赖IGMP协议。 下面我们分别说明: 一、动态加入(通过IGMP协议) 步骤: 1. 主机上的应用程序决定加入一个组(例如,通过socket编程中的setsockopt函数设置IP_ADD_MEMBERSHIP选项)。 2. 主机的网络协议栈会自动发送IGMP报告报文(Membership Report)给本地子网的路由器,声明加入。 3. 路由器收到报告后,会记录该接口下有主机加入了该,从而将后续收到的该的数据包转发到该子网。 动态加入的特点是:主机可以随时加入和离开(通过发送离开报文或路由器查询超时)。 二、静态加入 在某些情况下,可能需要主机永久监听某个,即使没有应用程序显式加入。这时可以通过操作系统配置静态。 例如: - 在Linux系统中,可以通过命令`ip route add`添加静态路由,或者通过修改配置文件将地址绑定到网卡。 - 在Windows系统中,可以通过路由命令添加永久路由。 静态加入的特点:主机不会发送IGMP报告,而是由网络管理员配置,使得主机始终接收该的流量。这种方式通常用于特殊场景,如网络设备管理、固定流接收等。 引用[3]中提到:主机的成员可以动态变化,主机有权选择加入或者退出某个主机。这进一步印证了动态加入是默认方式。 总结: 主机加入通常采用动态方式(通过IGMP协议),但也可以根据需要进行静态配置。 接下来,我们按照要求生成相关问题。</think>### 主机加入的方式:动态与静态详解 主机加入主要通过两种方式实现:**动态加入(IGMP协议)** 和 **静态配置**。这两种方式的核心区别在于**成员关系的维护机制**,具体对比如下: --- ### 一、动态加入(IGMP协议)[^1][^5] #### ▶ 工作流程 1. **路由器发起查询** 路由器定期发送 `IGMP Query` 报文(目的地址:224.0.0.1)到本地子网。 2. **主机响应报告** ```mermaid sequenceDiagram 主机->>路由器: IGMP Report (目标地址) 路由器->>主机: 开始转发该流量 ``` 主机通过 `IGMP Report` 报文声明加入特定(如239.255.255.250)。 3. **状态维护** 路由器通过周期性查询确认成员活跃性,超时无响应则停止转发流量。 #### ▶ 特点 - **即需即用**:主机可随时加入/离开 - **自动维护**:网络设备动态管理成员关系 - **资源优化**:仅在存在接收者时转发流量 --- ### 二、静态配置 #### ▶ 实现方式 1. **操作系统级配置** ```bash # Linux永久加入 ip route add 239.255.255.250/32 dev eth0 echo "239.255.255.250 eth0" >> /etc/ip-multicast-groups ``` 2. **网络设备绑定** ```cisco ! Cisco交换机配置 interface GigabitEthernet0/1 ip igmp static-group 239.255.255.250 ``` #### ▶ 特点 - **永久生效**:绕过IGMP协议,主机持续接收流 - **无状态维护**:不响应路由器的IGMP查询 - **适用场景**:关键服务(如视频监控中心[^3])、测试环境 --- ### 三、核心差异对比 | **特性** | 动态加入 (IGMP) | 静态配置 | |-------------------|-------------------------------|------------------------------| | 成员关系维护 | 路由器通过查询动态维护[^1] | 管理员手动配置 | | 协议依赖 | 必须启用IGMP协议[^5] | 完全绕过IGMP | | 成员状态 | 临时加入,超时离开 | 永久成员 | | 流量控制 | 按需转发 | 持续转发 | | 适用场景 | 普通应用(视频会议等) | 关键业务/固定接收点[^3] | --- ### 四、典型应用场景 1. **动态加入案例** 视频会议客户端临时加入接收流媒体: ```c // C语言示例: 使用setsockopt动态加入 struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = inet_addr("239.255.255.250"); mreq.imr_interface.s_addr = INADDR_ANY; setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); ``` 2. **静态配置案例** 安防监控中心永久接收所有摄像头流: ```bash # Linux配置静态路由 ip route add 239.1.1.0/24 dev eth0 permanent ``` --- ### 五、选择建议 - 优先使用 **动态IGMP**:适用于普通应用,节省网络资源[^2] - 考虑 **静态配置** 当: - 需要绕过IGMP协议(如测试环境) - 接收关键业务数据(容错性要求高) - 网络设备不支持IGMP协议 > **关键提示**:静态配置需确保网络设备同步设置,否则可能导致流量黑洞[^3]。跨VLAN场景需配合路由协议(如PIM-SM[^4])。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值