目录

1 概述

2 多播地址

3 多播地址与 MAC 地址的映射

3 Linux多播编程

3.1 套接口选项

3.2 组播包接收

3.3 组播包发送

3.4 测试结果


1 概述

单播用于两个主机之间的端对端通信,广播用于一个主机对整个局域网上所有主机上的数据通信。单播和广播是两个极端,要么对一个主机进行通信,要么对整个局域网上的主机进行通信。实际情况下,经常需要对一组特定的主机进行通信,而不是整个局域网上的所有主机,这就是多播的用途。

IP 多播(也称多址广播或组播)技术,是一种允许一台或多台主机(多播源)发送单一数据包到多台主机(一次的,同时的)的 TCP/IP 网络技术。多播是 IPv6 数据包的 3 种基本目的地址类型之一,多播是一点对多点的通信, IPv6 没有采用 IPv4 中的组播术语,而是将广播看成是多播的一个特殊例子。

多播作为一点对多点的通信,数据的收发仅仅在同一分组中进行,是节省网络带宽的有效方法之一。在网络应用中,当需要将一个节点的信号传送到多个节点时,无论是采用重复点对点通信方式,还是采用广播方式,都会严重浪费网络带宽,只有多播才是最好的选择。多播能使一个或多个多播源只把数据包发送给特定的多播组,而只有加入该多播组的主机才能接收到数据包。

IP 多播应用大致可以分为三类:点对多点应用,多点对点应用和多点对多点应用。

  1. 点对多点应用是指一个发送者,多个接收者的应用形式,这是最常见的多播应用形式。典型的应用包括:媒体广播、媒体推送、信息缓存、事件通知和状态监视等。
  2. 多点对点应用是指多个发送者,一个接收者的应用形式。通常是双向请求响应应用,任何一端(多点或点)都有可能发起请求。典型应用包括:资源查找、数据收集、网络竞拍、信息询问等。
  3. 多点对多点应用是指多个发送者和多个接收者的应用形式。通常,每个接收者可以接收多个发送者发送的数据,同时,每个发送者可以把数据发送给多个接收者。典型应用包括:多点会议、资源同步、并行处理、协同处理、远程学习、讨论组、分布式交互模拟(DIS)、多人游戏等。

2 多播地址

IP 多播通信必须依赖于 IP 多播地址,在 IPv4 中它是一个 D 类 IP 地址,范围从 224.0.0.0 到 239.255.255.255,并被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类:

  1. 局部链接多播地址范围在 224.0.0.0~224.0.0.255,这是为路由协议和其它用途保留的地址,路由器并不转发属于此范围的IP包;
  2. 预留多播地址为 224.0.1.0~238.255.255.255,可用于全球范围(如Internet)或网络协议;
  3. 管理权限多播地址为 239.0.0.0~239.255.255.255,可供组织内部使用,类似于私有 IP 地址,不能用于 Internet,可限制多播范围。

一些多播组地址被 IANA 确定为知名地址,它们也被当作永久主机组,这和 TCP 及 UDP 中的知名端口相似。同样,这些知名多播地址在 RFC 最新分配数字中列出,注意这些多播地址所代表的组是永久组,而它们的组成员却不是永久的。这些地址如下:

224.0.0.1    所有组播主机
224.0.0.2    所有组播路由器
224.0.0.4    DRMRP 路由器
224.0.0.5    所有 OSPF 的路由器
224.0.0.6    OSPF 指派路由器
224.0.0.9    RPIv2 路由器
224.0.0.10  EIGRP 路由器
224.0.0.13  PIM 路由器
224.0.0.22  IGMPv3
224.0.0.25  RGMP
224.0.1.1    NTP 网络时间协议

3 多播地址与 MAC 地址的映射

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

这个我们可以这样理解,多播地址就类似于 QQ 群号,多播组相当于 QQ 群,一个个的主机就相当于群里面的成员。

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

java 解决多网卡环境下使用特定网卡广播UDP消息的问题_UDP组播传输

由于多播地址( D类地址 )中的最高 5bit 在映射过程中被忽略,因此每个以太网多播地址对应的多播组是不唯一的。32 个不同的多播组号被映射为一个以太网地址。例如,多播地址 224.128.64.32(十六进制 e0.80.40.20)和 224.0.64.32(十六进制 e0.00.40.20)都映射为同一以太网地址 01:00:5e:00:40:20。

既然地址映射是不唯一的,那么设备驱动程序或 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层自动完成。

java 解决多网卡环境下使用特定网卡广播UDP消息的问题_UDP组播传输_02

3 Linux多播编程

3.1 套接口选项

int setsockopt( int sockfd, int level,int optname,const void *optval, socklen_t optlen );

java 解决多网卡环境下使用特定网卡广播UDP消息的问题_UDP组播传输_03


成功执行返回 0,否则返回 -1

选项 IP_ADD_MEMBERSHIP 和 IP_DROP_MEMBERSHIP

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

struct in_addr
{
    in_addr_t s_addr;
}

struct ip_mreq          
{ 
    struct in_addr imn_multiaddr; // 多播组 IP,类似于 QQ 群号
    struct in_addr imr_interface;   // 将要添加到多播组的 IP,类似于QQ 成员号
};

多播只能用 UDP 或原始 IP 实现,不能用 TCP。

3.2 组播包接收

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
#define PORT 10086
#define SIZE 128
 
 
int main(void)
{
    int ret = -1;
    int sockfd = -1;
    int i = 0;
    char buf[SIZE];
    struct sockaddr_in addr;
    struct sockaddr_in from;
    //组播相关结构体
    struct ip_mreq req;
    socklen_t len = sizeof(from);
 
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        perror("socket"); 
        goto err0;
    }
 
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = INADDR_ANY;
    //inet_pton(AF_INET, "172.16.1.88", &addr.sin_addr); 
 
    ret = bind(sockfd, (void*)&addr, sizeof(addr));
    if (-1 == ret)
    {
        perror("bind"); 
        goto err1;
    }
 
    printf("UDP Server %s: %d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
 
    //加入多播组
    req.imr_multiaddr.s_addr = inet_addr("224.0.0.88");
    //将本机加入多播组
    req.imr_interface.s_addr = INADDR_ANY;
 
    //加入多播组
    ret = setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &req, sizeof(req));
    if (ret < 0)
    {
        perror("setsockopt"); 
        goto err0;
    }
 
    while(1)
    {
        memset(buf, 0, SIZE);
 
        ret = recvfrom(sockfd, buf, SIZE, 0, (void*)&from, &len);
        buf[ret] = 0;
 
        printf("recv from: %s:%d  %s\n", inet_ntoa(from.sin_addr), ntohs(from.sin_port), buf);
        i++;
        //退出循环
        if (10 == i)
            break;
    }
 
    ret = setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &req, sizeof(req));
    if (ret < 0)
    {
        perror("setsockopt"); 
        goto err0;
    }
        
    //退出组播组之后 测试是否可以收到组播包
    while(1)
    {
        memset(buf, 0, SIZE);
 
        ret = recvfrom(sockfd, buf, SIZE, 0, (void*)&from, &len);
        buf[ret] = 0;
 
        printf("recv from: %s:%d  %s\n", inet_ntoa(from.sin_addr), ntohs(from.sin_port), buf);
        i++;
        //退出循环
        if (10 == i)
            break;
    }
 
    close(sockfd);
    return 0;
err1:
    close(sockfd);
err0:
    return -1;
}

以上代码编译运行时,可以会出现这样的错误:No such device。这主要和网络配置有关,需要配置组播路由,具体如下:

route add -net 224.0.0.88 netmask 255.255.255.255 eth0

//其它辅助命令:
sudo route del -net 224.0.0.88 netmask 255.255.255.255 eth0 //把224.0.0.88从路由表中删除
route -n //查看路由表信息

3.3 组播包发送

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
#define PORT 10086
#define SIZE 128
 
int main(void)
{
    int ret = -1;
    int sockfd = -1;
    int i = 0;
    char buf[SIZE];
    struct sockaddr_in peer;
 
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        perror("socket"); 
        goto err0;
    }
 
    memset(&peer, 0, sizeof(peer));
    peer.sin_family = AF_INET;
    peer.sin_port = htons(PORT);
    inet_pton(AF_INET, "224.0.0.88", &peer.sin_addr); 
 
    printf("send data to UDP Server %s: %d\n", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));
 
    //向多播组发送消息
    while(1)
    {
        sprintf(buf, "hello world %d", i); 
        ret = sendto(sockfd, buf, strlen(buf), 0, (void*)&peer, sizeof(peer));
        printf("ret: %d\n", ret);
        sleep(1);
        i++; 
    }
    return 0;
err0:
    return -1;
}

3.4 测试结果

发送端:

java 解决多网卡环境下使用特定网卡广播UDP消息的问题_组播_04

 接收端:

java 解决多网卡环境下使用特定网卡广播UDP消息的问题_IP_05