网络中的一台主机如果希望能够接收到来自网络中其它主机发往某一个组播组的数据报,那么这么主机必须先加入该组播组,然后就可以从组地址接收数据包。在广域网中,还涉及到路由器支持组播路由等,但本文希望以一个最为简单的例子解释清楚协议栈关于组播的一个最为简单明了的工作过程,甚至,我们不希望涉及到 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已经可以接收组播数据了,关于组播数据如何接收,下回分解。

【网络开发】UDP组播接收端解析的更多相关文章

  1. C# 使用UDP组播实现局域网桌面共享

    最近需要在产品中加入桌面共享的功能,暂时不用实现远程控制:参考了园子里的一些文章,加入了一些自己的修改. 需求:将一台机器的桌面通过网络显示到多个客户端的屏幕上,显示内容可能为PPT,Word文档之类 ...

  2. (转)C# 使用UDP组播实现局域网桌面共享

    转:http://www.cnblogs.com/mobwiz/p/3715743.html 最近需要在产品中加入桌面共享的功能,暂时不用实现远程控制:参考了园子里的一些文章,加入了一些自己的修改. ...

  3. Android设备一对多录屏直播--(UDP组播连接,Tcp传输)

    原文:https://blog.csdn.net/sunmmer123/article/details/82734245 近期需要学习流媒体知识,做一个Android设备相互投屏Demo,因此找到了这 ...

  4. ffmpeg笔记——UDP组播接收总结

    ffmpeg在avformat_open_input里面已经实现了UDP的协议,所以只需要设置好参数,将url传递进去就可以了. 和打开文件的方式基本一样: 01 AVCodecContext *pV ...

  5. Android上UDP组播无法接收数据的问题

    最近,想做一个跨平台的局域网的文件传输软件,思路是组播设备信息,TCP连接传输文件.于是进行了一次简单的UDP组播测试,发现Android对于UDP组播接收数据的支持即极为有限. 部分代码如下 pac ...

  6. 多网卡情况下接收udp组播

    多网卡下接收udp组播 往往会接收失败 因为用错了网卡 例如我想要接收2网段 其他电脑出的udp组播  我电脑有有线网和wifi在window下可以这样 route add 230.0.0.1 mas ...

  7. Android开发:组播(多播)与广播

    近期由于需要编写能够使同一局域网中的Android客户端与PC端进行自动匹配通信功能的程序,学习并试验了JAVA组播与广播的内容,记录一些理解如下: 一.组播(多播) 背景知识:组播使用UDP对一定范 ...

  8. 调皮的udp组播技术

    2017年本科毕业,经历过千辛万苦的找工作之后,我进入了现在的这家公司.虽是职场小白,但励志成为IT界的一股清流(毕竟开发的妹子少,哈哈).因为公司的业务需要,我负责的部分是利用组播技术实现OSG模型 ...

  9. 桌面共享UDP组播实现

    组播(Multicast)传输:在发送者和每一接收者之间实现点对多点网络连接.如果一台发送者同时给多个的接收者传输相同的数据,也只需复制一份的相同数据包.它提高了数据传送效率.减少了骨干网络出现拥塞的 ...

随机推荐

  1. learning java 字符串常用操作

    // 字符串索引取值 "; System.)); // 字符串比较 "; "; "; System.out.println(s1.compareTo(s2)); ...

  2. 2-STM32+W5500+GPRS物联网开发基础篇-基础篇学习的内容

    https://www.cnblogs.com/yangfengwu/p/10936553.html 这次的基础篇为公开篇,将公开所有基础篇的资料和源码 现在说一下基础篇准备公开的内容:(大部分哈,要 ...

  3. 洛谷 P3258 [JLOI2014]松鼠的新家 题解

    P3258 [JLOI2014]松鼠的新家 题目描述 松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个房间,并且有n-1根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的.天哪,他 ...

  4. combox使用自定义的model列表中无元素显示

    自定义的model(stationModel)中有 name 和point两种属性名. 初始化stationModel Combobox{ textRole: 'name' model:station ...

  5. 【概率论】6-3:中心极限定理(The Central Limit Theorem)

    title: [概率论]6-3:中心极限定理(The Central Limit Theorem) categories: - Mathematic - Probability keywords: - ...

  6. (10)打鸡儿教你Vue.js

    事件处理器 <div id="app"> <button v-on:click="counter += 1">增加 1</butt ...

  7. Three.js中的div标签跟随(模型弹框)

    目录 Three.js中的div标签跟随(模型弹框) 参考官方案例 核心渲染器 用法 注意事项 Three.js中的div标签跟随(模型弹框) 参考官方案例 核心渲染器 three.js-master ...

  8. JS-选项卡制作解释部分

    <!DOCTYPE html> <html> <head> <meta name="author" content "郭菊锋,7 ...

  9. html5中 input的pattern属性 和 details/summary元素

    html5--3.21 课程小结与其他新增元素 一.总结 一句话总结: input的pattern属性可以设置正则验证,比如检测学号的位数和数字区间 details/summary元素配合起来可以做元 ...

  10. DELPHI开发LINUX插件架构的程序

    DELPHI开发LINUX插件架构的程序 DELPHI可以开发LINUX配置型插件架构的程序,并且这一套插件架构,同样适用于MSWINDOWS和MAC. 配置插件: 根据配置,动态加载插件: