广播和多播

  广播和多播仅应用于UDP,TCP是一个面向连接的协议,它意味着分别运行于两主机(由IP地址确定)内的两进程(由端口号确定)间存在一条连接。

主机对由信道传送过来帧的过滤过程:

  1. 首先,网卡查看由信道传送过来的帧,确定是否接收该帧,若接收后就将它传往设备驱动程序。通常网卡仅接收那些目的地址为网卡物理地址或广播地址的帧。另外,多数接口均被设置为混合模式,这种模式能接收每个帧的一个复制。目前,大多数的网卡经过配置都能接收目的地址为多播地址或某些子网多播地址的帧。对于以太网,当地址中最高字节的最低位设置为1时表示该地址是一个多播地址,用十六进制可表示为01 : 00 : 00 : 00 : 00 : 00(以太网广播地址ff : ff : ff : ff : ff : ff可看作是以太网多播地址的特例)。
  2. 如果网卡收到一个帧,这个帧将被传送给设备驱动程序(如果帧检验和错,网卡将丢弃该帧)。设备驱动程序将进行另外的帧过滤。首先,帧类型中必须指定要使用的协议( IP、ARP等等)。其次,进行多播过滤来检测该主机是否属于多播地址说明的多播组。
  3. 设备驱动程序随后将数据帧传送给下一层,比如,当帧类型指定为IP数据报时,就传往IP层。IP根据IP地址中的源地址和目的地址进行更多的过滤检测。如果正常,就将数据报传送给下一层(如TCP或UDP)。
  4. 每次UDP收到由IP传送来的数据报,就根据目的端口号,有时还有源端口号进行数据报过滤。如果当前没有进程使用该目的端口号,就丢弃该数据报并产生一个ICMP不可达报文(TCP根据它的端口号作相似的过滤)。如果UDP数据报存在检验和错,将被丢弃。

广播

用途

  1. 地子网定位一个服务器主机,前提是已知或认定这个服务器主机属于本地子网,但是不知道他的单播IP地址,这种操作也称为资源发现。
  2. 有多个客户主机与单个服务器主机通信的局域网环境中尽量减少分组流通。处于这个目的使用广播的因特网应用有多个例子。

分类

  1. 受限的广播地址:255 . 255 . 255 . 255。该地址用于主机配置过程中IP数据报的目的地址,此时,主机可能还不知道它所在网络的网络掩码,甚至连它的IP地址也不知道。路由器不会转发受限广播的数据包,但同一个子网的所有主机都会接收到受限广播的数据包。
  2. 指向网络的广播地址:指向网络的广播地址是主机号为全1的地址。A类网络广播地址为netid . 255 . 255 . 255,其中netid为A类网络的网络号。一个路由器必须转发指向网络的广播,但它也必须有一个不进行转发的选择。
  3. 子网定向广播地址:作为制定子网上所有接口的广播地址。举例说明,如果我们有一个192.168.42/24子网,那么192.168.42.255就是该子网上所有接口的子网定向广播地址。直接广播可以被路由转发,发送到目标网络的所有主机
  4. 指向所有子网的广播:需要了解目的网络的子网掩码,以便与指向网络的广播地址区分开。指向所有子网的广播地址的子网号及主机号为全1。要求将一个指向所有子网的广播传送给所有子网,但当前的路由器没有这么做。这很幸运,因为一个因错误配置而没有子网掩码的主机会把它的本地广播传送到所有子网。

单播和广播比较

  向一个单播地址发送一个数据报时,它会对它进行层层加头,首先UDP层给它加一个UDP首部,IP层给他加一个IPV4首部,数据链路曾会通过ARP将目的IP地址转换一个以太网地址,该分组然后作为一个目的的以太网地址为这个48位地址的以太网帧发出去,该以太网帧类型字段表示ipv4分组0x0800,ipv6分组0x86dd。

  中间主机的以太网接口看到该帧后把它的目的以太网地址与自己的以太网地址比较,既然他们不一致,就忽略这个帧,可见单播帧不会对该主机造成任何开销,因为他忽略他们的是接口而不是主机。

单播

广播

在广播中接收数据的流程:

  1. 根据以太网地址比较决定该网络接口是否接受数据
  2. 在以太网层,驱动程序从接口内存读取帧,根据以太网帧类型决定ip输入队列(ipv4:0x0800,ipv6:0x86dd)
  3. 在ip层,比较目的ip地址是否为本机ip之一,若是则被接受;接着查看该分组ipv4首部中的协议字段,其值为表示udp的17,该分组被传到udp层
  4. udp层:检查udp数据报的目的端口(如果udp数据报已连接,那么还检查源端口),接着在本例中把数据报至于相应的套接字队列,必要的话udp层还作为内核一部分唤醒阻塞在相应输入操作的进程上,由内核读取这个新收取的数据报

  单播:单播数据报仅由通过目的的ip指定的单个主机接受,子网上的其他主机不受影响。

  广播:子网上未参加相应广播应用的所有主机也不得不沿协议栈一路上向完成整的处理收取的udp广播数据包,直到该数据报历经udp层被丢弃,子网上所有非IP主机也不得不在数据链路层接收完整的帧,然后再丢弃。

  UDP客户指定通信的目的IP地址是192.168.42.255(广播地址),这样网卡发送出去的以太网帧的目的MAC地址是以太网广播地址ff:ff:ff:ff:ff:ff。子网上所有收到这个以太网帧的主机的网卡驱动程序都会将帧交给上层协议栈处理。

  然而中间那个主机没有任何进程绑定520端口,该主机的udp代码丢弃这个已收取数据报。该主机绝不发送一个icmp端口不可达信息,因为这么做可能产生广播风暴,即子网上大量主机几乎同时产生一个响应,导致网络在一段时间内不可用。另外这些ICMP错误如果处理也成了错误:有的接收主机报告了错误,有的没报告。

多播

I P多播提供两类服务:

  1. 向多个目的地址传送数据。有许多向多个接收者传送信息的应用:例如交互式会议系统和向多个接收者分发邮件或新闻。如果不采用多播,目前这些应用大多采用T C P来完成(向每个目的地址传送一个单独的数据复制)。然而,即使使用多播,某些应用可能继续采用TCP来保证它的可靠性。
  2. 客户对服务器的请求。例如,无盘工作站需要确定启动引导服务器。目前,这项服务是通过广播来提供的,但是使用多播可降低不提供这项服务主机的负担。

  IP 多播通信必须依赖于 IP 多播地址,在 IPv4 中它是一个 D 类 IP 地址,范围从 224.0.0.0 到 239.255.255.255。

  分配的28 bit均用作多播组号而不再表示其他。

多播地址与以太网地址的转换

  使用同一个 IP 多播地址接收多播数据包的所有主机构成了一个主机组,也称为多播组。一个多播组的成员是随时变动的,一台主机可以随时加入或离开多播组,多播组成员的数目和所在的地理位置也不受限制,一台主机也可以属于几个多播组。

  IPv4 的 D 类地址是多播地址。IEEE 把一块以太网多播组地址分给 IANA 以支持IP多播。块的地址都以 01:00:5e 开头,第25 位为 0,低 23 位为 IPv4 多播地址( D类地址 )的低 23 位。

  由于多播地址( D类地址 )中的最高 5bit 在映射过程中被忽略,因此每个以太网多播地址对应的多播组是不唯一的。为了指明一个多播地址,任何一个以太网地址的首字节必须是01,32 个不同的多播组号被映射为一个以太网地址(32个多播地址会映射成单个以太网地址)。

  既然地址映射是不唯一的,那么设备驱动程序或 IP 层就必须对数据报进行过滤。因为网卡可能接收到主机不想接收的多播数据帧,如下图,假如主机 1 加入的多播为 224.128.64.32, 主机 2 加入的多播为 224.0.64.32,我们想给 224.0.64.32 所在的多播组 ( 主机 2 ) 发送信息,数据经过网卡时,224.128.64.32 (主机 1 ) 和 224.0.64.32 (主机 2 ) 所在多播组的网卡都会收到数据,因为它们的 MAC 地址都是 01:00:5e:00:40:20。这时候,如果网卡不提供足够的多播数据帧过滤功能,设备驱动程序就必须接收所有多播数据帧,然后对它们进行过滤,这个过滤过程是网络驱动或IP层自动完成。

  右侧主机上的接收应用进程启动,并创建一个UDP套接字,捆绑端口123到该套接字上,然后加入多播组224.0.1.1。这种“加入”操作是通过调用setsockopt完成。

         上述操作完成之后,IPv4层内部保存这些信息,并告知合适的数据链路接收目的以太网地址为01:00:5e:00:01:01的以太网帧。该地址就是接收应用进程刚加入的多播地址对应的以太网地址。

         下一个步骤是左侧主机上的发送应用进程创建一个UDP套接字,往IP地址224.0.1.1的123端口发送一个数据报。发送多播数据报无需任何特殊处理:发送应用进程不必为此加入多播组。

         发送主机把该IP地址转换成相应的以太网目的地址,再发送承载该数据报的以太网帧。注意该帧中同时含有目的以太网地址和目的IP地址。

         假设中间主机不只备IPv4多播能力。它将完全忽略该帧,因为:

  1. 该帧的目的以太网地址不匹配该主机的接口地址;
  2. 该帧的目的以太网地址不是以太网广播地址;
  3. 该主机的接口未被告知接收任何组地址(高序字节的低序位被置为1的以太网地址)

         该帧基于我们所称的“不完备过滤”被右侧主机的数据链路接收,其中的过滤操作由相应接口使用该帧的以太网目的地址执行。我们之所以说这种过滤不完备是因为尽管告知该接口接收以某个特定以太网组地址为目的地址的帧,通常它也会接收以其他以太网组地址为目的地址的帧。

         当我们告知一个以太网接口接收目的地址为某个特定以太网组地址的帧时。许多当前的以太网接口卡对这个地址应用某个散列(hash)函数,计算出一个介于0和511之间的值。当有一个目的地为某个组地址的帧在线缆上经过时,接口对其目的地址应用同样的散列函数,计算出一个介于0和511之间的值。如果该值与之前的值匹配,那就接收这个帧;否则忽略这个帧。

         右侧主机的数据链路收取该帧后,把由该帧承载的分组传递到IP层,因为该以太网帧的类型为IPv4。既然收到的分组以某个多播IP地址作为目的地址,IP层于是比较该地址和本机的接收应用进程已经加入的所有多播地址,根据比较结果确定是接受还是丢弃该分组。我们称这个操作为完备过滤,因为它基于IPv4报头中完整的32位D类地址执行。在本例子中,IP层接受该分组并把承载在其中的UDP数据报传递到UDP等,UDP层再把承载在UDP数据报中的应用数据报传递到绑定了端口123的套接字。

  该图没有展示以下三种情况:

  1. 运行所有加入多播地址为255.0.1.1的某个应用进程主机。既然多播地址组id高在到以太网地址的映射中被忽略,该主机的接口也将接收目的以太网地址为01:00:5e:00:01:01的帧,这种情况该帧承载的分组将由ip层中的完备过滤丢弃
  2. 运行所加入多播地址符合以下某个应用进程的一个主机:由这个多播地址映射成的以太网地址恰好和01:00:5e:00:01:01一样被该主机执行非完备过滤的接口散列到同一个结果,该接口也将接收目的的以太网地址为01:00:5e:00:01:01的帧,直到数据链路层或ip层丢弃
  3. 目的地为相同多播组(244.0.1.1)不同端口(如:4000)的一个数据报,右侧主机仍然接收该数据报,并由ip层接收传递给udp层,不过udp层将丢弃它(假设绑定4000端口的进程不存在)

多播的缺点

  与单播相比,多播没有补包机制,因为组播采用的是UDP的传输方式,并且不是针对一个接受者,所以无法有针对的进行补包。所以直接用组播协议传输的数据是不可靠的。

根据接收者对组播源处理方式的不同,组播模型分为以下两大类:
  ASM模型:即任意源组播模型。在ASM模型中,任一发送者都可作为组播源向某组播组地址发送组播信息,接收者通过加入由该组播组地址标识的组播组以获得发往该组播组的组播信息。在ASM模型中,接收者无法预先知道组播源的位置,但可以在任意时间加入或离开组播组。
  SSM模型:即指定信源组播模型。在现实生活中,用户可能只对某些组播源发送的组播信息感兴趣,而不愿接收其它源发送的信息。SSM模型为用户提供了一种能够在客户端指定组播源的传输服务。

在ASM模型下,接收者无法选择组播源,只能被动地接收所有组播源的信息,而SSM模型的提出则为指定源组播提供了解决方案。

广域网上的多播

  多播相对于广播的优势在于不会给对多播分组不感兴趣的主机增加额外负担。广域网上也可以使用多播,比如下图

假设其中的5台主机启动了某个程序,这5个进程都加入了一个给定多播组,另外假设每个多播路由器与其相邻多播路由器的通信使用某个多播路由协议,以MRP指称。

         当某个主机上的一个进程加入一个多播组时,该主机向所有直接连接的多播路由器发送一个IGMP消息,告知它们本主机已加入了那个多播组,多播路由器随后使用MRP交换这些信息,这样每个多播路由器就知道在收到目的地为所加入多播地址的分组时该如何处理。

         假设左上方主机的一个进程开始发送目的地为那个给定多播地址的分组。如下图:

 跟踪这些多播分组从发送进程游走到所有接收进程所经历的步骤如下:

  1. 这些分组由发送进程多播发送,接收主机H1接收这些分组(因为它已经加入给定多播组),多播路由器MR1也接收这些分组(因为每个多播路由器都必须接收所有多播分组)。
  2. MR1把这些多播分组转发到MR2,因为MRP已经告知MR1:MR2需要接收目的地为给定多播组的分组。
  3. MR2在直接连接的局域网上多播发送这些分组,因为该局域网上的主机H2和H3属于该多播组。MR2还向MR3发送这些分组的一个副本。
  4. 像MR2那样对分组进行复制是多播转发所特有的。单播分组在被路由器转发时从不被复制。
  5. MR3把这些多播分组发送到MR4,但是不在直接连接的局域网上多播这此分组,因为我们假设该局域网上没有主机加入该多播组。
  6. MR4在直按连接的局域网上多播发送这些分组,因为该局域网上的主机H4和HS属于该多播组。它并不向MRS发送这些分组的一个副本,因为直接连接MR5的局域网上没有主  机属于该多播组,而MR4已经根据与MRS交换的多播路由信息知道这一点。

多播编程

  1. 建立一个socket;
  2. 设置多播的参数,例如超时时间TTL,本地回环许可LOOP等
  3. 加入多播组
  4. 发送和接收数据
  5. 从多播组离开

多播程序设计使用setsockopt()函数和getsockopt()函数来实现,组播的选项是IP层的。

 

getsockopt()/setsockopt()的选项

    义

IP_MULTICAST_TTL

设置多播组数据的TTL值

IP_ADD_MEMBERSHIP

在指定接口上加入组播组

IP_DROP_MEMBERSHIP

退出组播组

IP_MULTICAST_IF

获取默认接口或设置接口

IP_MULTICAST_LOOP

禁止组播数据回送

IP_MULTICASE_TTL
  选项IP_MULTICAST_TTL允许设置超时TTL,范围为0~255之间的任何值,例如:

unsigned char ttl=255;
setsockopt(s,IPPROTO_IP,IP_MULTICAST_TTL,&ttl,sizeof(ttl));

IP_MULTICAST_IF
  选项IP_MULTICAST_IF用于设置组播的默认默认网络接口,会从给定的网络接口发送,另一个网络接口会忽略此数据。例如:

struct in_addr addr;
setsockopt(s,IPPROTO_IP,IP_MULTICAST_IF,&addr,sizeof(addr));

参数addr是希望多播输出接口的IP地址,使用INADDR_ANY地址回送到默认接口。
  默认情况下,当本机发送组播数据到某个网络接口时,在IP层,数据会回送到本地的回环接口,选项IP_MULTICAST_LOOP用于控制数据是否回送到本地的回环接口。例如:

unsigned char loop;
setsockopt(s,IPPROTO_IP,IP_MULTICAST_LOOP,&loop,sizeof(loop)); //参数loop设置为0禁止回送,设置为1允许回送。

IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP(ASM模型)
加入或者退出一个多播组,通过选项IP_ADD_MEMBERSHIP和IP_DROP_MEMBER- SHIP,对一个结构struct ip_mreq类型的变量进行控制,struct ip_mreq原型如下:

struct ip_mreq 
{ 
    struct in_addr imn_multiaddr; /*加入或者退出的广播组IP地址*/ 
    struct in_addr imr_interface; /*加入或者退出的网络接口IP地址*/
};

  选项IP_ADD_MEMBERSHIP用于加入某个多播组,之后就可以向这个多播组发送数据或者从多播组接收数据。此选项的值为mreq结构,成员imn_multiaddr是需要加入的多播组IP地址,成员imr_interface是本机需要加入广播组的网络接口IP地址。例如:

struct ip_mreq mreq;
setsockopt(s,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));

带源地址的IP多播(SSM模型)

  带源地址的IP多播允许加入组时指定要接收哪些成员的数据。有两种方式:

  • “包含”方式,这种方式下,为套接字指定N个有效源地址,套接字仅接收来自这些源地址的数据;使用IP_ADD_SOURCE_MEMBERSHIP和IP_DROP_SOURCE_MEMBERSHIP。
  •  “排除”方式,这种方式下,为套接字指定N个源地址,套接字将接收来自这些源地址之外的数据;使用IP_BLOCK_SOURCE(排除某个源地址)和IP_UNBLOCK_SOURCE(从排除集合中移除此源地址)以上两种方式输入参数都是ip_mreq_source结构。

server

/*
*broadcast_server.c - 多播服务程序
*/
#define MCAST_PORT 8888;
#define MCAST_ADDR "224.0.0.100"/    /*一个局部连接多播地址,路由器不进行转发*/
#define MCAST_DATA "BROADCAST TEST DATA"            /*多播发送的数据*/
#define MCAST_INTERVAL 5                            /*发送间隔时间*/

int main(int argc, char*argv)
{
    int s;
    struct sockaddr_in mcast_addr;     

    s = socket(AF_INET, SOCK_DGRAM, 0);         /*建立套接字*/
    if (s == -1)
    {
        perror("socket()");
        return -1;
    }

    memset(&mcast_addr, 0, sizeof(mcast_addr));/*初始化IP多播地址为0*/

    mcast_addr.sin_family = AF_INET;                /*设置协议族类行为AF*/
    mcast_addr.sin_addr.s_addr = inet_addr(MCAST_ADDR);/*设置多播IP地址*/
    mcast_addr.sin_port = htons(MCAST_PORT);        /*设置多播端口*/
                                                    /*向多播地址发送数据*/
    while(1) {

        int n = sendto(s,                           /*套接字描述符*/
                       MCAST_DATA,     /*数据*/
                       sizeof(MCAST_DATA),    /*长度*/
                       0,
                       (struct sockaddr*)&mcast_addr,
                        sizeof(mcast_addr)) ;
        if( n < 0)
        {
            perror("sendto()");
            return -2;

        }  
    
        sleep(MCAST_INTERVAL);                          /*等待一段时间*/
    }

    return 0;
}
View Code

client

/*
*broadcast_client.c - 多播的客户端
*/

#define MCAST_PORT 8888;
#define MCAST_ADDR "224.0.0.100"     /*一个局部连接多播地址,路由器不进行转发*/
#define MCAST_INTERVAL 5                        /*发送间隔时间*/
#define BUFF_SIZE 256                           /*接收缓冲区大小*/

int main(int argc, char*argv[])
{  
    int s;                                      /*套接字文件描述符*/
    struct sockaddr_in local_addr;              /*本地地址*/
    int err = -1;   

    s = socket(AF_INET, SOCK_DGRAM, 0);     /*建立套接字*/
    if (s == -1)
    {
        perror("socket()");
        return -1;
    }  

    /*初始化地址*/
    memset(&local_addr, 0, sizeof(local_addr));

    local_addr.sin_family = AF_INET;
    local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    local_addr.sin_port = htons(MCAST_PORT); 

    /*绑定socket*/
    err = bind(s,(struct sockaddr*)&local_addr, sizeof(local_addr)) ;
    if(err < 0)
    {
        perror("bind()");
        return -2;
    }
   
    /*设置回环许可*/
    int loop = 1;

    err = setsockopt(s,IPPROTO_IP, IP_MULTICAST_LOOP,&loop, sizeof(loop));
    if(err < 0)
    {
        perror("setsockopt():IP_MULTICAST_LOOP");
        return -3;
    }

    struct ip_mreq mreq;   
     
   /*加入多播组*/
    mreq.imr_multiaddr.s_addr = inet_addr(MCAST_ADDR); /*多播地址*/
    mreq.imr_interface.s_addr = htonl(INADDR_ANY); /*网络接口为默认*/

    /*将本机加入多播组*/
    err = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq, sizeof(mreq));//ASM模型
    if (err < 0)
    {
        perror("setsockopt():IP_ADD_MEMBERSHIP");
        return -4;
    }

    int times = 0;
    int addr_len = 0;
    char buff[BUFF_SIZE];
    int n = 0;

    /*循环接收多播组的消息,5次后退出*/
    for(times = 0;times<5;times++)
    {
        addr_len = sizeof(local_addr);

        memset(buff, 0, BUFF_SIZE);  /*清空接收缓冲区*/

        /*接收数据*/
        n = recvfrom(s, buff, BUFF_SIZE, 0,(struct sockaddr*)&local_addr,&addr_len);
        if( n== -1)
        {
            perror("recvfrom()");
        }

        /*打印信息*/
        printf("Recv %dst message from server:%s\n", times, buff);

        sleep(MCAST_INTERVAL);
    }

    /*退出多播组*/
    err = setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP,&mreq, sizeof(mreq));
      
    close(s);
    return 0;
}
View Code

 

posted on 2019-11-28 20:45  tianzeng  阅读(1009)  评论(0编辑  收藏  举报

导航