广播、组播的引入
我们可以发现之前的编程中都是 一对一 与 一对多 的模型,广播与多播将报文同时传往多个接收者的应用来说十分重要,主要应用于UDP传输层,因为TCP协议中若发送端发送数据时,有一个客户端未接受到,他会要求发送端重新发送数据,这样其他接受到的客户端将再次接受到数据,导致数据很容易发生混乱
一、广播简介
- 子网广播地址:指定子网上所有接口的广播地址 ,例如子网192.168.1.0/24的广播地址为192.168.1.255。
- 受限广播地址:255.255.255.255,该地址用于主机配置过程中 I P数据报的目的地址,主机可能还不知道它所在网络的网络掩码,甚至连它的 I P地址也不知道。路由器都不转发目的地址为受限的广播地址的数据报,这样的数据报仅出现在本地网络中
- 指向网络的广播地址:n e t i d . 2 5 5 . 2 5 5 . 2 5 5,其中n e t i d为A/B/C/D/E类网络的网络号(D类为多播)
- 指向所有子网的广播:指向所有子网的广播也需要了解目的网络的子网掩码,以便与指向网络的广播地址区分开。指向所有子网的广播地址的子网号及主机号为全 1
对象:无限制,服务器、客户端均可接受和发送,因此实现的代码可一致,但是用户要求在同一子网,并且端口一样,因为端口号需一致,所以该实验就无法在一个虚拟机上进行,要多开一个虚拟机。
广播的应用:
- ARP数据报:获取目标IP主机的MAC地址。
- DHCP协议:将尚未分配IP地址的主机,通过向连接的子网内DHCP发送请求,获取IP号。
- 还有其他的一些服务:如路由进程
广播的发送与接收过程
广播的MAC为FFF,IP为(子网,-1),端口自定义,只有端口相同的用户才能接收。
用户发送广播数据报中,首先封装UDP段报头,在封装IP报(广播地址),在IP报这里会返回一份数据报给用户,紧接着在封装链路层帧报头,链路层(网卡)将其发送到该子网下的所有主机的链路层(网卡),接收主机识别到MAC地址为fff,拆机数据链路层数据帧,到网络层发现为广播IP,在进行拆解到传输层UDP,判断是否有对应的端口,有则发生到用户进程,没有则丢包。
通过上面的流程:我们可以知道广播可以顺利的传输到同一子网的每台主机的传输层。
附上广播代码
#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 BROADCAST_PORT 8000
#define MAXLINE 128
int main(void){
int ser_fd,i;
char buf[MAXLINE] = "wo.ai.yehuiling";
char ipstr[INET_ADDRSTRLEN];
ssize_t len;
struct sockaddr_in boardcastaddr,cliaddr;
socklen_t cliaddr_len;
int on = 1;
//1.socket
ser_fd = socket(AF_INET, SOCK_DGRAM, 0);
setsockopt(ser_fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(int));
bzero(&cliaddr,sizeof(cliaddr));
cliaddr.sin_family = AF_INET;
cliaddr.sin_port = htons(BROADCAST_PORT);
cliaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(ser_fd, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
bzero(&boardcastaddr,sizeof(boardcastaddr));
boardcastaddr.sin_family = AF_INET;
boardcastaddr.sin_port = htons(BROADCAST_PORT);
inet_pton(AF_INET, "192.168.31.255", &boardcastaddr.sin_addr.s_addr);
sendto(ser_fd, buf, MAXLINE, 0,(struct sockaddr *)&boardcastaddr,
sizeof(boardcastaddr));
//3.recevform
while(1){
cliaddr_len = sizeof(cliaddr);
len = recvfrom(ser_fd, buf, MAXLINE, 0,(struct sockaddr *)&cliaddr,
&cliaddr_len);
//print client ip and port
printf("received from s_addr %s at PORT %d\n",\
inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ipstr, sizeof(ipstr)),
ntohs(cliaddr.sin_port));
write(1, buf, len);
write(1, "\n", 2);
sendto(ser_fd, buf, MAXLINE, 0,(struct sockaddr *)&boardcastaddr,
sizeof(boardcastaddr));
sleep(2);
}
//4.close
close(ser_fd);
}
二、组(多)播的引入
解决广播中不干扰某些不关注该信息的主机,只有客户端加入指定多播组才能接受到消息,能够接收发往一个特定多播组地址数据的主机集合称为主机组 (host group)。一个主机组可跨越多个网络。主机组中成员可随时加入或离开主机组。主机组中对主机的数量没有限制,同时不属于某一主机组的主机可以向该组发送信息。。
组播组可以是永久的也可以是临时的。组播组地址中,有一部份由官方分配的永久组播组。永久组播组保存不变的是他的ip地址,组中的成员构成可以发生变化,永久组播组中成员的数量都可以是任意的,甚至可以为零。那些没有保留下来供永久组播组使用的ip组播地址,可以被临时组播组利用。
组播IP地址:224.0.0.0至239.255.255.255
组播以太网地址(MAC):01:00:5e:00:00:00至01:00:5e:7f:ff:ff
组播的IP地址到组播以太网的映射
例如目的MAC地址:01:00:5e:xx:xx:xx,ip:224.25.25.25的mac地址为:01:00:5e:19:19:19。
可见将IP的低三字节映射到mac地址中。
与广播的区别
- 发送端的数据帧不同
广播MAC地址(6字节)为:全F
广播IP地址(4字节)为:当前子网192.168.31.255或255.255.255.255
组播IP地址:224.0.0.0至239.255.255.255
组播以太网地址(MAC):01:00:5e:00:00:00至01:00:5e:7f:ff:ff - 接收客户,广播的用户需要在同一子网,组播只需要用户加入到组播组中
IGMP协议
IGMP提供了在转发组播数据包到目的地的最后阶段所需的信息,实现如下双向的功能:
- 主机通过IGMP通知路由器希望接收或离开某个特定组播组的信息。
- 路由器通过IGMP周期性地查询局域网内的组播组成员是否处于活动状态,实现所连网段组成员关系的收集与维护。
用户如何加入到组播组
组播分两部分:组播数据流(上面的数据报)和组播控制流
组播控制流分三种报文:report(join)、leave、iquery,其中report和leave为用户设置的加入与离开,而iquery为路由器主动发出,无需设置
通过setsockopt修改socket套字节选项。发送控制报文
组播用户设置:
- IP_MULTICAST_LOOP:是否支持本机上的回环,即报文是否发一份给本机
- IP_ADD_MEMBERSHIP:加入组播组
- IP_DROP_MEMBERSHIP:离开组播组
多播编程实现
将所有需要接收的用户加入到组播组中,发送端可以不加入到组播组中,组播地址需在上方图表所示范围,所有加入到组播组的用户均可发送消息到组播组,在组播组的用户均能接收。
- 发送端(一般为服务器):发送的消息需对socket的IP段添加组播地址,还有固定组播的端口号。
- 接收端:必须加入到组播组中,并且接收消息的IP段需设置为组播地址,端口号与组播对应发送的端口号一致。
代码实现功能:
只有一个用户的例子,该用户加入组播组,并且设置支持本机上的回环,从标准输入输入数据发送数据到组播组中,自身接收并打印。
附上多播代码
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#define MCAST_PORT 8001
#define MCAST_ADDR "224.25.0.2"
#define MAXLINE 80
int main(int argc, char *argv[]){
struct sockaddr_in mcastvaddr,cliaddr;
socklen_t cliaddr_len;
struct ip_mreqn group;
int listenfd,on = 1;
char buf[MAXLINE];
char recvbuf[MAXLINE];
//1.socket
listenfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&cliaddr, sizeof(cliaddr));
cliaddr.sin_family = AF_INET;
cliaddr.sin_addr.s_addr = htonl(INADDR_ANY);
cliaddr.sin_port = htons(MCAST_PORT);
bind(listenfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
//3.加入多播组
bzero(&group, sizeof(group));
inet_pton(AF_INET, MCAST_ADDR, &group.imr_multiaddr);
inet_pton(AF_INET, "0.0.0.0", &group.imr_address);
group.imr_ifindex = if_nametoindex("etho");
if(setsockopt(listenfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group))<0){
perror("setsockopt");
return -1;
}
if(setsockopt(listenfd, IPPROTO_IP, IP_MULTICAST_LOOP, &on, sizeof(int))<0){
perror("setsockopt");
return -1;
}
//2.初始化组播IP
bzero(&mcastvaddr,sizeof(mcastvaddr));
mcastvaddr.sin_family = AF_INET;
mcastvaddr.sin_port = htons(MCAST_PORT);
inet_pton(AF_INET, MCAST_ADDR, &mcastvaddr.sin_addr.s_addr);
//3.do
while(1){
printf("input str:");
fgets(buf, MAXLINE, stdin);
sendto(listenfd, buf, strlen(buf), 0, (struct sockaddr *)&mcastvaddr,sizeof(mcastvaddr));
recvfrom(listenfd, recvbuf, MAXLINE, 0, NULL, 0);
printf("recv buf :");
fflush(stdout);
write(1,recvbuf,strlen(recvbuf));
}
//4.end
close(listenfd);
return 0;
}