第十四章(多播与广播)学习笔记
2019-11-18
22:12:21
参考:https://github.com/riba2534/TCP-IP-NetworkNote/tree/master/ch14
14.1.1 多播的数据传输方式以及流量方面的优点
多播的数据传输特点可整理如下:
- 多播服务器端针对特定多播组,只发送 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 次。但是此时如果用多播网络传输文件,则只需要发送一次。这时由 1000 台主机构成的网络中的路由器负责复制文件并传递到主机。就因为这种特性,多播主要用于「多媒体数据实时传输」。
另外,理论上可以完成多播通信,但是不少路由器并不支持多播,或即便支持也因网络拥堵问题故意阻断多播。因此,为了在不支持多播的路由器中完成多播通信,也会使用隧道(Tunneling)技术。
14.1.2 路由(Routing)和 TTL(Time to Live,生存时间),以及加入组的办法
为了传递多播数据包,必须设置 TTL 。TTL 是 Time to Live的简写,是决定「数据包传递距离」的主要因素。TTL 用整数表示,并且每经过一个路由器就减一。TTL 变为 0 时,该数据包就无法再被传递,只能销毁。因此,TTL 的值设置过大将影响网络流量。当然,设置过小,也无法传递到目标。
接下来是 TTL 的设置方法。TTL 是可以通过第九章的套接字可选项完成的。与设置 TTL 相关的协议层为 IPPROTO_IP ,选项名为 IP_MULTICAST_TTL。因此,可以用如下代码把 TTL 设置为 64
int send_sock;
int time_live = 64;
...
send_sock=socket(PF_INET,SOCK_DGRAM,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_adr;
...
recv_sock=socket(PF_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(time_live);
...
下面是 ip_mreq 结构体的定义
struct ip_mreq
{
struct in_addr imr_multiaddr; //写入加入组的IP地址
struct in_addr imr_interface; //加入该组的套接字所属主机的IP地址
};
:
14.1.3 实现多播 Sender 和 Receiver
多播中用「发送者」(以下称为 Sender) 和「接收者」(以下称为 Receiver)替代服务器端和客户端。顾名思义,此处的 Sender 是多播数据的发送主体,Receiver 是需要多播组加入过程的数据接收主体。下面是示例,示例的运行场景如下:
- Sender : 向 AAA 组广播(Broadcasting)文件中保存的新闻信息
- Receiver : 接收传递到 AAA 组的新闻信息。
下面是两个代码:
news_sender.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <arpa/inet.h> 6 #include <sys/socket.h> 7 8 #define TTL 64 9 #define BUF_SIZE 30 10 void error_handling(char *message); 11 12 int main(int argc, char *argv[]) 13 { 14 int send_sock; 15 struct sockaddr_in mul_adr; 16 int time_live=TTL; 17 FILE *fp; 18 char buf[BUF_SIZE]; 19 20 if(argc!=3){ 21 printf("Usage : %s <GroupIP> <PORT>\n", argv[0]); 22 exit(1); 23 } 24 25 send_sock=socket(PF_INET, SOCK_DGRAM, 0); 26 memset(&mul_adr, 0, sizeof(mul_adr)); 27 mul_adr.sin_family=AF_INET; 28 mul_adr.sin_addr.s_addr=inet_addr(argv[1]); // Multicast IP 29 mul_adr.sin_port=htons(atoi(argv[2])); // Multicast Port 30 31 setsockopt(send_sock, IPPROTO_IP, 32 IP_MULTICAST_TTL, (void*)&time_live, sizeof(time_live)); 33 34 if((fp=fopen("news.txt", "r"))==NULL) 35 error_handling("fopen() error"); 36 37 while(!feof(fp)) /* Broadcasting */ 38 { 39 fgets(buf, BUF_SIZE, fp); 40 sendto(send_sock, buf, strlen(buf), 41 0, (struct sockaddr*)&mul_adr, sizeof(mul_adr)); 42 sleep(2); 43 } 44 fclose(fp); 45 close(send_sock); 46 return 0; 47 } 48 49 void error_handling(char *message) 50 { 51 fputs(message, stderr); 52 fputc('\n', stderr); 53 exit(1); 54 }
news_receiver.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <arpa/inet.h> 6 #include <sys/socket.h> 7 8 #define BUF_SIZE 30 9 void error_handling(char *message); 10 11 int main(int argc, char *argv[]) 12 { 13 int recv_sock; 14 int str_len; 15 char buf[BUF_SIZE]; 16 struct sockaddr_in adr; 17 struct ip_mreq join_adr; 18 19 if(argc!=3) { 20 printf("Usage : %s <GroupIP> <PORT>\n", argv[0]); 21 exit(1); 22 } 23 24 recv_sock=socket(PF_INET, SOCK_DGRAM, 0); 25 memset(&adr, 0, sizeof(adr)); 26 adr.sin_family=AF_INET; 27 adr.sin_addr.s_addr=htonl(INADDR_ANY); 28 adr.sin_port=htons(atoi(argv[2])); 29 30 if(bind(recv_sock, (struct sockaddr*) &adr, sizeof(adr))==-1) 31 error_handling("bind() error"); 32 33 join_adr.imr_multiaddr.s_addr=inet_addr(argv[1]); 34 join_adr.imr_interface.s_addr=htonl(INADDR_ANY); 35 36 setsockopt(recv_sock, IPPROTO_IP, 37 IP_ADD_MEMBERSHIP, (void*)&join_adr, sizeof(join_adr)); 38 39 while(1) 40 { 41 str_len=recvfrom(recv_sock, buf, BUF_SIZE-1, 0, NULL, 0); 42 if(str_len<0) 43 break; 44 buf[str_len]=0; 45 fputs(buf, stdout); 46 } 47 close(recv_sock); 48 return 0; 49 } 50 51 void error_handling(char *message) 52 { 53 fputs(message, stderr); 54 fputc('\n', stderr); 55 exit(1); 56 }
首先先创建news.txt ,往里面写入I LOVE YOU !!!!
14.2.2 实现广播数据的 Sender 和 Receiver
下面是广播数据的 Sender 和 Receiver的代码:
news_sender_brd.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <arpa/inet.h> 6 #include <sys/socket.h> 7 8 #define BUF_SIZE 30 9 void error_handling(char *message); 10 11 int main(int argc, char *argv[]) 12 { 13 int send_sock; 14 struct sockaddr_in broad_adr; 15 FILE *fp; 16 char buf[BUF_SIZE]; 17 int so_brd=1; 18 19 if(argc!=3) { 20 printf("Usage : %s <Boradcast IP> <PORT>\n", argv[0]); 21 exit(1); 22 } 23 24 send_sock=socket(PF_INET, SOCK_DGRAM, 0); 25 memset(&broad_adr, 0, sizeof(broad_adr)); 26 broad_adr.sin_family=AF_INET; 27 broad_adr.sin_addr.s_addr=inet_addr(argv[1]); 28 broad_adr.sin_port=htons(atoi(argv[2])); 29 30 setsockopt(send_sock, SOL_SOCKET, 31 SO_BROADCAST, (void*)&so_brd, sizeof(so_brd)); 32 if((fp=fopen("news.txt", "r"))==NULL) 33 error_handling("fopen() error"); 34 35 while(!feof(fp)) 36 { 37 fgets(buf, BUF_SIZE, fp); 38 sendto(send_sock, buf, strlen(buf), 39 0, (struct sockaddr*)&broad_adr, sizeof(broad_adr)); 40 sleep(2); 41 } 42 43 close(send_sock); 44 return 0; 45 } 46 47 void error_handling(char *message) 48 { 49 fputs(message, stderr); 50 fputc('\n', stderr); 51 exit(1); 52 }
news_receiver_brd.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <arpa/inet.h> 6 #include <sys/socket.h> 7 8 #define BUF_SIZE 30 9 void error_handling(char *message); 10 11 int main(int argc, char *argv[]) 12 { 13 int recv_sock; 14 struct sockaddr_in adr; 15 int str_len; 16 char buf[BUF_SIZE]; 17 18 if(argc!=2) { 19 printf("Usage : %s <PORT>\n", argv[0]); 20 exit(1); 21 } 22 23 recv_sock=socket(PF_INET, SOCK_DGRAM, 0); 24 25 memset(&adr, 0, sizeof(adr)); 26 adr.sin_family=AF_INET; 27 adr.sin_addr.s_addr=htonl(INADDR_ANY); 28 adr.sin_port=htons(atoi(argv[1])); 29 30 if(bind(recv_sock, (struct sockaddr*)&adr, sizeof(adr))==-1) 31 error_handling("bind() error"); 32 33 while(1) 34 { 35 str_len=recvfrom(recv_sock, buf, BUF_SIZE-1, 0, NULL, 0); 36 if(str_len<0) 37 break; 38 buf[str_len]=0; 39 fputs(buf, stdout); 40 } 41 42 close(recv_sock); 43 return 0; 44 } 45 46 void error_handling(char *message) 47 { 48 fputs(message, stderr); 49 fputc('\n', stderr); 50 exit(1); 51 }
14.4 习题
以下答案仅代表本人个人观点,可能不是正确答案。
-
TTL 的含义是什么?请从路由器的角度说明较大的 TTL 值与较小的 TTL 值之间的区别及问题。
答:TTL 是决定「数据包传递距离」的主要因素。TTL 每经过一个路由器就减一。TTL 变为 0 时,数据包就无法再被传递,只能销毁。因此,TTL设置过大会影响网络流量。当然,设置过小无法传递到目标。
-
多播与广播的异同点是什么?请从数据通信的角度进行说明。
答:在「一次性向多个主机发送数据」这一点上与多播类似,但传输的数据范围有区别。多播即使在跨越不同网络的情况下,只要加入多播组就能接受数据。相反,广播只能向同意网络中的主机传输数据。
-
下面关于多播的说法描述错误的是?
答:以下内容加粗的为描述正确
- 多播是用来加入多播组的所有主机传输数据的协议
- 主机连接到同一网络才能加入到多播组,也就是说,多播组无法跨越多个网络
- 能够加入多播组的主机数并无限制,但只能有 1个主机(Sender)向该组发送数据
- 多播时使用的套接字是 UDP 套接字,因为多播是基于 UDP 进行数据通信的。
-
多播也对网络流量有利,请比较 TCP 交换方式解释其原因
答:TCP 是必须建立一对一的连接,如果要向1000个主机发送文件,就得传递1000次。但是此时用多播方式传输数据,就只需要发送一次。
-
多播方式的数据通信需要 MBone 虚拟网络。换言之,MBone 是用于多播的网络,但它是虚拟网络。请解释此处的「虚拟网络」
答:可以理解为「通过网络中的特殊协议工作的软件概念上的网络」。也就是说, MBone 并非可以触及的物理网络。他是以物理网络为基础,通过软件方法实现的多播通信必备虚拟网络。