Linux网络编程(十二)——多播与广播

文章目录

12 多播与广播

12.1 多播

12.1.1 多播的数据传输方式及流量方面的优点

12.1.2 路由(Routing)和 TTL(Time to Live,生存时间)以及加入组的办法

12.1.3 实现多播 Sender 和 Receiver

12.2 广播

12.2.1 广播的理解及实现方法

12.2.2 实现广播数据的Sender和Receiver


12 多播与广播

12.1 多播

多播(Multicast)方式的数据传输是基于 UDP 完成的。因此 ,与 UDP 服务器端/客户端的实现方式非常接近。区别在于,UDP 数据传输以单一目标进行,而多播数据同时传递到加入(注册)特定组的大量主机。换言之,采用多播方式时,可以同时向多个主机传递数据。

12.1.1 多播的数据传输方式及流量方面的优点

多播的数据传输特点可整理如下:

  • 多播服务器端针对特定多播组,只发送 1 次数据。
  • 即使只发送 1 次数据,但该组内的所有客户端都会接收数据
  • 多播组数可以在 IP 地址范围内任意增加
  • 加入特定组即可接收发往该多播组的数据

多播组是 D 类IP地址(224.0.0.0~239.255.255.255)。多播是基于 UDP 完成的,也就是说,多播数据包的格式与 UDP 数据包相同。只是与一般的 UDP 数据包不同。向网络传递 1 个多播数据包时,路由器将复制该数据包并传递到多个主机。如下图所示,多播需要借助路由器完成。

若通过 TCP 或 UDP 向60个主机发送文件,则共需要传递60次。即便将10台主机合为1个网络,使99%的传输路径相同的情况下也是如此。但此时若使用多播方式传输文件,则只需发送1次。这时由60台主机构成的网络中的路由器负责复制文件并传递到主机。就因为这种特性,多播主要用于“多媒体数据的实时传输”。

12.1.2 路由(Routing)和 TTL(Time to Live,生存时间)以及加入组的办法

为了传递多播数据包,必须设置 TTLTTL 是 Time to Live 的简写,是决定 “数据包传递距离” 的主要因素。TTL 用整数表示,并且每经过一个路由器就减一。TTL 变为 0 时,该数据包就无法再被传递,只能销毁。因此,TTL 的值设置过大将影响网络流量。当然,设置过小,也无法传递到目标。

接下来给出 TTL 设置方法。程序中的 TTL 设置是通过套接字可选项完成的。与设置 TTL 相关的协议层为 IPPROTO_IP,选项名为 IP_MULTICAST_TTL。因此,可以用如下代码把TTL设置为64。

int time_tive = 64;
send_sock = socket(AF_INET,SOCK_DGRAM,0);
setsockopt(send_sock,IPPROTO_IP,IP_MULTICAST_TTL,(void*)&time_live,sizeof(time_live));

另外,加入多播组也需要通过设置套接字可选项来完成。加入多播组相关协议层IPPROTO_IP,选项名为 IP_ADD_MEMBERSHIP。因此,可通过如下代码加入多播组

struct ip_mreq join_adr;
recv_sock=socket(AF_INET,SOCK_DGRAM,0);
join_adr.imr_multiaddr.s_addr="多播组地址信息";
join_adr.imr_interface.s_addr="加入多播组的主机地址信息";
setsockopt(recv_sock,IPPROTO_IP,IP_ADD_MEMBERSHIP,(void*)&join_adr,sizeof(join_adr);

//其中,ip_mreq结构体结构如下所示
struct ip_mreq {
    struct in_addr imr_multiaddr; //写入加入组的IP地址
    struct in_addr imr_interface; //加入该组的套接字所属主机的IP地址
};

12.1.3 实现多播 Sender 和 Receiver

多播中用 “发送者”( Sender) 和 “接收者”(Receiver)替代服务器端和客户端。顾名思义,此处的 Sender 是多播数据的发送主体,Receiver 是需要多播组加入过程的数据接收主体。示例的运行场景如下:

  • Sender : 向 AAA 组广播(Broadcasting)文件中保存的新闻信息
  • Receiver : 接收传递到 AAA 组的新闻信息。

(1)发送者 news_sender.c

#define TTL 64
#define BUF_SIZE 1024

int main(int argc, char const *argv[])
{
    struct sockaddr_in mul_addr;
    int time_live = TTL;
    char* buf = malloc(sizeof(char)*BUF_SIZE);
    if(argc != 3) {
        printf("Usage:%s <GroupIP> <port>\n",argv[0]);
        exit(1);
    }
    mul_addr.sin_family = AF_INET;
    mul_addr.sin_port = htons(atoi(argv[2]));
    inet_pton(AF_INET,argv[1],&mul_addr.sin_addr);
    //设置数据包生存时间
    int send_sock = socket(AF_INET,SOCK_DGRAM,0);
    setsockopt(send_sock,IPPROTO_IP,IP_MULTICAST_TTL,(void*)&time_live,sizeof(time_live));
    int fd = open("news.txt",O_RDONLY);
    if(fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    int str_len;
    //读取news.txt内容到buf缓冲区,再向多播地址发送数据
    while((str_len=read(fd,buf,BUF_SIZE))>0) {
        //因为自己就是组播地址,所以sendto后两个参数填写mul_addr
        sendto(send_sock,buf,str_len,0,(struct sockaddr*)&mul_addr,sizeof(mul_addr));
        sleep(2);
    }
    close(fd);
    close(send_sock);
    return 0;
}

(2)接收者 news_receiver.c

Sender 与普通的UDP套接字程序相比差别不大,但多播 Receiver 则有些不同。为了接收传向任意多播地址的数据,需要经过加人多播组的过程。

int main(int argc, char const *argv[])
{
    struct sockaddr_in adr;
    struct ip_mreq join_adr;
    char* buf = malloc(sizeof(char)*BUF_SIZE);
    memset(&adr,0,sizeof(adr));
    if(argc != 3) {
        printf("Usage:%s <GroupIP> <port>\n",argv[0]);
        exit(1);
    }
    adr.sin_family = AF_INET;
    adr.sin_port = htons(atoi(argv[2]));
    inet_pton(AF_INET,argv[1],&adr.sin_addr);
    int recv_sock = socket(AF_INET,SOCK_DGRAM,0);

    //设置可选项 加入多播组
    join_adr.imr_multiaddr.s_addr = inet_addr(argv[1]);         //多播组地址
    join_adr.imr_interface.s_addr = htonl(INADDR_ANY);          //加入多播组的主机地址
    setsockopt(recv_sock,IPPROTO_IP,IP_ADD_MEMBERSHIP,(void*)&join_adr,sizeof(join_adr));
    int temp = bind(recv_sock,(struct sockaddr*)&adr,sizeof(adr));
    handle_error("bind",temp);
    while(1) {
        memset(buf,0,BUF_SIZE);
        //已经加入多播组,不需要直到发送方的地址信息
        int str_len = recvfrom(recv_sock,buf,BUF_SIZE,0,NULL,0);
        if(str_len<0) break;
        puts(buf);
    }
    close(recv_sock);
    return 0;
}

recvfrom 函数的最后两个参数为什么是0?因为在多播中,接收方通常只关心从多播组接收到的数据,而不关心数据是从哪个具体的发送方发送的。因此,可以将 src_addraddrlen 设置为 NULL 或 0,表示不需要获取发送方的地址信息。 

(3)测试结果

12.2 广播

广播(Broadcast)在 “一次性向多个主机发送数据” 这一点上与多播类似,但传输数据的范围有区别。多播即使在跨越不同网络的情况下,只要加入多播组就能接受数据。相反,广播只能向同一网络中的主机传输数据。

12.2.1 广播的理解及实现方法

广播是向同一网络中的所有主机传输数据的方法。与多播相同,广播也是通过 UDP 来完成的。根据传输数据时使用的IP地址形式,广播分为以下两种:

  • 直接广播(Directed Broadcast)
  • 本地广播(Local Broadcast)

二者在实现上的差别主要在于IP地址。直接广播的IP地址中除了网络地址外,其余主机地址全部设置成 1。例如,向C类网络地址 192.12.34.x 中的所有主机传输数据时,可以向 192.12.34.255 传输。换言之,可以采取直接广播的方式向特定区域内所有主机传输数据。反之,本地广播中使用的IP地址限定为 255.255.255.255 。例如,192.32.24.x 网络中的主机向 255.255.255.255 传输数据时,数据将传输到 192.32.24 网络中所有主机。

数据通信中使用的IP地址是与 UDP 示例的唯一区别。默认生成的套接字会阻止广播,因此,只需通过如下代码更改默认设置。

int bcast = 1;    //对变量进行初始化以将SO_BROADCAST选项信息改为1
int send_sock=socket(AF_INET,SOCK_DGRAM,0);
setsockopt(send_sock,SOL_SOCKET,SO_BROADCAST,(void*)&bcast,sizeof(bcast));

12.2.2 实现广播数据的Sender和Receiver

(1)发送端 news_sender_brd.c

int main(int argc, char const *argv[])
{
    int so_brd = 1;
    struct sockaddr_in mul_addr;
    int time_live = TTL;
    char* buf = malloc(sizeof(char)*BUF_SIZE);
    if(argc != 3) {
        printf("Usage:%s <GroupIP> <port>\n",argv[0]);
        exit(1);
    }
    mul_addr.sin_family = AF_INET;
    mul_addr.sin_port = htons(atoi(argv[2]));
    inet_pton(AF_INET,argv[1],&mul_addr.sin_addr);
    int send_sock = socket(AF_INET,SOCK_DGRAM,0);
    //设置数据广播
    setsockopt(send_sock,SOL_SOCKET,SO_BROADCAST,(void*)&so_brd,sizeof(so_brd));
    //设置数据包生存时间
    setsockopt(send_sock,IPPROTO_IP,IP_MULTICAST_TTL,(void*)&time_live,sizeof(time_live));
    int fd = open("news.txt",O_RDONLY);
    if(fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    int str_len;
    while((str_len=read(fd,buf,BUF_SIZE))>0) {
        sendto(send_sock,buf,str_len,0,(struct sockaddr*)&mul_addr,sizeof(mul_addr));
        sleep(2);
    }
    close(fd);
    close(send_sock);
    return 0;
}

(2)接收端 news_receiver_brd.c

int main(int argc, char const *argv[])
{
    struct sockaddr_in adr;
    struct ip_mreq join_adr;
    char* buf = malloc(sizeof(char)*BUF_SIZE);
    memset(&adr,0,sizeof(adr));
    if(argc != 2) {
        printf("Usage:%s <port>\n",argv[0]);
        exit(1);
    }
    adr.sin_family = AF_INET;
    adr.sin_port = htons(atoi(argv[1]));
    inet_pton(AF_INET,"0.0.0.0",&adr.sin_addr);
    int recv_sock = socket(AF_INET,SOCK_DGRAM,0);
    int temp = bind(recv_sock,(struct sockaddr*)&adr,sizeof(adr));
    handle_error("bind",temp);
    while(1) {
        memset(buf,0,BUF_SIZE);
        int str_len = recvfrom(recv_sock,buf,BUF_SIZE,0,NULL,0);
        if(str_len<0) break;
        puts(buf);
    }
    close(recv_sock);
    return 0;
}

(3)测试结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值