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

    使用BiDecimal类是了为防止运算时精度丢失: var f1 = new BigDecimal("0.05"); var f2 = BigDecimal.valueOf(0.0 ...

  2. [C++11]C++可变参数模板

    可变参数模板 原文链接: http://blog.csdn.net/xiaohu2022/article/details/69076281 普通模板只可以采取固定数量的模板参数.然而,有时候我们希望模 ...

  3. luogu 1714

    前缀和 + rmq #include <bits/stdc++.h> const int N = 5e5 + 10; int Pow[30], Log[N]; int n, m; int ...

  4. PHP ltrim() 函数

    例子 <?php $str = "Hello World!"; echo $str . "<br>"; echo ltrim($str,&qu ...

  5. 模板 - 数学 - 数论 - Min_25筛

    终于知道发明者的正确的名字了,是Min_25,这个筛法速度为亚线性的\(O(\frac{n^{\frac{3}{4}}}{\log x})\),用于求解具有下面性质的积性函数的前缀和: 在 \(p\) ...

  6. CF1205题解

    B 最高有\(64\)位,当\(n\le 128\)时,最坏情况形成不了三元环,\(floyed\)暴力做 否则直接输出\(3\) C 题意的\(n\)均为奇数,设\((i,j)\),把\(i+j\) ...

  7. ZR#1004

    ZR#1004 解法: 对于 $ (x^2 + y)^2 \equiv (x^2 - y)^2 + 1 \pmod p $ 化简并整理得 $ 4x^2y \equiv 1 \pmod p $ 即 $ ...

  8. Ibatis自动解决sql注入机制

    疑问1:为什么IBatis解决了大部分的sql注入?(实际上还有部分sql语句需要关心sql注入,比如like) 之前写Java web,一直使用IBatis,从来没有考虑过sql注入:最近写php( ...

  9. BDD本质及与ATDD区别

    说起BDD,你会想到什么?   在刚接触BDD(Behavior Driven Development,行为驱动开发)的时候,我以为就是用Cucumber这样的工具来编写场景用例,从而实现自动化测试, ...

  10. PostgreSQL中定时job执行(pgAgent)

    PostgreSQL中定时job执行 业务分析 近期项目需要定期清理数据库中的多余数据,即每月1号删除指定表中一年以上的数据. 初步分析这种定时job可以使用一下两种技术实现: Linux的cront ...