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

    easyui181版本下载地址 简单java+easyui181版本demo下载 注意前端页面的ajax请求路径对应后端 如果再加强样式可使用adminLTE

  2. js 中的方法注入(aop)

    js 中的方法注入 java中很多框架支持 apo 的注入, js中也可以类似的进行实现 主要是通过扩展js中方法的老祖 Function 对象来进行实现. Function.prototype.af ...

  3. 模板 - 字符串/数据结构 - 字典树/Trie

    使用静态数组的nxt指针的设计,大概比使用map作为nxt指针的设计要快1倍,但空间花费大概也大1倍.在数据量小的情况下,时间和空间效率都不及map<vector,int>.map< ...

  4. SpringMVC将通过ajax发送的 json数据封装成JavaBean

    SpringMVC将通过ajax发送的 json数据封装成JavaBean 通过ajax发送的 json数据封装成JavaBean对发送时有如下要求: 1.发送的数据类型必须时UTF-8 2.发送的必 ...

  5. arts 打卡12周

    一 算法:  字符串转换整数 (atoi)   请你来实现一个 atoi 函数,使其能将字符串转换成整数. 首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止. 当我们寻找 ...

  6. python使用ThreadPoolExecutor每秒并发5个

    import time from concurrent.futures import ThreadPoolExecutor from functools import partial from log ...

  7. 360杯复赛流量分析题 详细writeup

    题目名: 这是捕获的黑客攻击数据包 通过分析流量包,得知黑客先上传了一个文件: 追踪TCP流,可以看到文件内容,是一个木马: 然后通过get请求一个加密key,在响应里能看到key的值. 接下来就是用 ...

  8. laravel删除文件

    laravel删除文件 一.总结 一句话总结: 1.注意disk:disk决定路径 2.删单个文件的时候就用删单个文件的方式,别用删多个文件的方式(也就是参数别数组) public function ...

  9. Unity制作王者荣耀商业级手游

    <王者荣耀>这种现象级手机游戏是如何制作出来的呢?本文以<王者荣耀>MOBO类型的多人在线战术竞技游戏为入口,覆盖Unity游戏制作开发前端与Node.js服务器端的开发必备知 ...

  10. input标签type="file"上传文件的css样式

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...