TCP/IP网络编程 第十四章:多播与广播

多播

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


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

多播的数据传输特点可整理如下。
□多播服务器端针对特定多播组,只发送1次数据。
□即使只发送1次数据,但该组内的所有客户端都会接收数据。
□多播组数可在IP地址范围内任意增加。
□加入特定组即可接收发往该多播组的数据。
多播组是D类IP地址(224.0.0.0~239.255.255.255),“加入多播组”可以理解为通过程序完成
如下声明:“在D类IP地址中,我希望接收发往目标239.234.218.234的多播数据。”
多播是基于UDP完成的,也就是说,多播数据包的格式与UDP数据包相同。只是与一般的UDP数据包不同,向网络传递1个多播数据包时,路由器将复制该数据包并传递到多个主机。

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

另外,虽然理论上可以完成多播通信,但不少路由器并不支持多播,或即便支持也因网络拥堵问题故意阻断多播。因此,为了在不支持多播的路由器中完成多播通信,也会使用隧道(Tunneling)技术(这并非多播程序开发人员需要考虑的问题)。我们只讨论支持多播服务的环境下的编程方法。

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

接下来讨论多播相关编程方法。为了传递多播数据包,必需设置TTL。TTL是Time to Live的简写,是决定"数据包传递距离"的主要因素。TTL用整数表示,并且每经过一个路由器就减1。TTL变为0时,该数据包无法再被传递,只能销毁。因此,TTL的值设置将影响网络流量。

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

int send_sock;
int time_live=64;
.....
send_sock=socket(PF_INET,SOCK_DREAM,0);
setsockopt(send_sock,IPPROTO_IP,IP_MULTICAST_TTL,(void*)&time_live,sizeof(time_live));
...

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

int recv_sock;
struct ip_mreq join_addr;
....
recv_sock=socket(PF_INET,SOCK_DREAM,0);
....
join_addr.imr_mutiadr.s_addr="多播组地址信息";
join_addr.imr_interface.s_addr="加入多播组的主机地址信息";
setsockopt(recv_sock,IPPROTO_IP,IP_ADD_MEMBERSHIP,(void*)&join_addr,sizeof(join_addr));
....

此处再讲解一下ip_mreq结构体,该结构体的定义如下。

struct ip_mreq{
     struct in_addr imr_multiaddr;
     struct in_addr imr_interface;
}

第三章讲过in_addr结构体,因此只介绍结构体成员。首先,第一个成员imr_multiaddr中写入加入组的组IP地址。第二个成员imr_interface是加入该组的套接字所属主机的IP地址,也可以使用INADDR_ANY。

实现多播 Sender 和 Receiver

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

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

接下来给出Sender代码。Sender比Receiver简单,因为Receiver需要经过加入组的过程,而
Sender只需创建UDP套接字,并向多播地址发送数据。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define TTL 64
#define BUF_SIZE 30
void error_hangling(char *message);

int main(int argc,char *argv[]){
    int send_sock;
    struct sockaddr_in mul_addr;
    int time_live=TTL;
    FILE *fp;
    char buf[BUF_SIZE];
    if(argc!=3){
         printf("Usage : %s <GroupIP><PORT>\n",argv[0]);
         exit(1);
    }

    send_sock=socket(PF_INET,SOCK_DREAM,0);
    memset(&mul_addr,0,sizeof(mul_addr));
    mul_addr.sin_family=AF_INET;
    mul_addr.sin_addr_s.addr=inet_addr(argv[1]);//多播组IP
    mul_addr.sin_port=htons(atoi(argv[2]));//多播组端口

    setsockopt(send_sock,IPPROTO_IP,IP_MULTICAST_TTL,(void*)&time_live,sizeof(time_live));
    if((fp=fopen("news.txt","r"))==NULL)error_handling("fopen() error");

    while(!feof(fp)){//广播内容
        fgets(buf,BUF_SIZE,fp);
        sendto(send_sock,buf,strlen(buf),0,(struct sockaddr*)&mul_addr,sizeof(mul_addr));
        //上面也可以优化为持久连接
        sleep(2);
    }
    fclose(fp);
    close(send_sock);
    return 0;
}

void error_handling(char *message){
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}

接下来给出与上述示例结合使用的Receiver程序。

#include<"与发送端的头文件声明相同,故省略">
#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc,char *argv[]){
    int recv_sock;
    int str_len;
    char buf[BUF_SIZE];
    struct sockaddr_in addr;
    struct ip_mreq join_addr;
    if(argc!=3){
        printf("Usage : %s <GroupIP> <PORT>\n",argv[0]);
        exit(1);
    }

    recv_sock=socket(PF_INET,SOCK_DREAM,0);
    memset(&addr,0,sizeof(addr));
    addr.sin_family=AF_INET;
    addr.sin_addr.s_addr=htonl(INADDR_ANY);
    addr.sin_port=htons(atoi(argv[2]));

    if(bind(recv_sock,(struct sockaddr*)&addr,sizeof(addr))==-1)
         error_handling("bind() error");

    join_addr.imr_multiaddr.s_addr=inet_addr(argv[1]);
    join_addr.imr_interface.s_addr=htonl(INADDR_ANY);
 
    setsockopt(recv_sock,IPPROTO_IP,IP_ADDR_MEMBERSHIP, 
    (void*)&join_addr,sizeof(join_addr));

    while(1){
        str_len=recvfrom(recv_sock,buf,BUF_SIZE-1,0,NULL,0);
        if(str_len<0)break;
        buf[str_len]=0;
        fputs(buf,stdout);
    }
    close(recv_sock);
    return 0;
}

void error_handling(char *message){
    //和发送端的内容相同,故省略
}

广播

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

广播的理解及实现方法

广播是向同一网络中的所有主机传输数据的方法。与多播相同,广播也是基于UDP完成的。
根据传输数据时使用的IP地址的形式,广播分为如下2种。
□直接广播(Directed Broadcast)
□本地广播(Local Broadcast)
二者在代码实现上的差别主要在于IP地址。

直接广播的IP地址中除了网络地址外,其余主机地址全部设置为1。例如,希望向网络地址192.12.34中的所有主机传输数据时,可以向192.12.34.255传输。换言之,可以采用直接广播的方式向特定区域内所有主机传输数据。
反之,本地广播中使用的IP地址限定为255.255.255.255。例如,192.32.24网络中的主机向
255.255.255.255传输数据时,数据将传递到192.32.24网络中的所有主机。
那么,应当如何实现Sender和Receiver呢?实际上,如果不仔细观察广播示例中通信时使用的IP地址,则很难与UDP示例进行区分。也就是说,数据通信中使用的IP地址是与UDP示例的唯一区别。默认生成的套接字会阻止广播、因此。只需通过如下代码更改默认设置。

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


调用setsockopt函数,将SO_BROADCAST选项设置为bcast变量中的值1。这意味着可以进行
数据广播。当然,上述套接字选项只需在Sender中更改,Receiver的实现不需要该过程。

实现广播数据的Sender和Receiver

下面实现基于广播的Sender和Receiver。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#incldue<arpa/inet.h>
#incldue<sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[]){
    int send_sock;
    struct sockaddr_in broad_adr;
    FILE *fp;
    char buf[BUF_SIZE];
    int so_brd=1;
    if(argc!=3){
         printf("Usage : %s <Boradcast IP><PORT>\n",argv[e]);
         exit(1);
    }

    send_sock=socket(PF_INET,SOCK_DGRAM,0);
    memset(&broad_adr, 0, sizeof(broad_adr));
    broad_adr.sin_family=AF_INET;
    broad_adr.sin_addr.s_addr=inet_addr(argv[1]);
    broad_adr.sin_port=htons(atoi(argv[2]));

    setsockopt(send sock, SOL_SOCKET,SO_BROADCAST,(void*)&so_brd, sizeof(so_brd));
    if((fp=fopen("news.txt","r"))==NULL)error_handling("fopen() error");

    while(!feof(fp)){
       fgets(buf,BUF_SIZE,fp);
       sendto(send_sock,buf,strlen(buf),0,(struct sockaddr*)&broad_adr, sizeof(broad_adr));
       sleep(2);
    }
    close(send_sock);
    return 0;
}

void error_handling(char *message){
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

第29行更改第23行创建的UDP套接字的可选项,使其能够发送广播数据,其余部分和UDPsender一致。接下来给出广播Receiver。

#include<"与上一篇示例头文件声明相同,故省略">
#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[]){
    int recv_sock;
    struct sockaddr_in adr;
    int str_len;
    char buf[BUF_SIZE];
    if(argc!=2){
       printf("Usage : %s <PORT>\n", argv[0]);
       exit(1);
    }

    recv_sock=socket(PF_INET,SOCK_DGRAM,0);
    memset(&adr,0, sizeof(adr));
    adr.sin_family=AF_INET;
    adr.sin_addr.s_addr=htonl(INADDR_ANY);
    adr.sin_port=htons(atoi(argv[1]));

    if(bind(recv_sock,(struct sockaddr*)&adr, sizeof(adr))==-1)
           error_handling("bind() error");
    while(1){
    str_len=recvfrom(recv_sock, buf, BUF_SIZE-1, 0, NULL, 0);
    if(str_len<0)
        break;
    buf[str_len]=0;
    fputs(buf,stdout);
    }
    close(recv_sock);
    return 0;
}

void error_handling(char *message)
   //与上一个函教一致,故省略
}

基于Windows的实现

在Windows平台上实现上述示例无需改动,因为之前的内容同样适用于此。只是在多播中,头文件示例稍有区别。需要增加ws2tcpip.h的声明,该头文件中定义了IP_MULTICAST_TTL选项和ip_mreq结构体。

猜你喜欢

转载自blog.csdn.net/Reol99999/article/details/131804005
今日推荐