基于UDP传输层的广播与组播模型(简单易懂)

广播、组播的引入

我们可以发现之前的编程中都是 一对一 与 一对多 的模型,广播与多播将报文同时传往多个接收者的应用来说十分重要,主要应用于UDP传输层,因为TCP协议中若发送端发送数据时,有一个客户端未接受到,他会要求发送端重新发送数据,这样其他接受到的客户端将再次接受到数据,导致数据很容易发生混乱

一、广播简介

  1. 子网广播地址:指定子网上所有接口的广播地址 ,例如子网192.168.1.0/24的广播地址为192.168.1.255。
  2. 受限广播地址:255.255.255.255,该地址用于主机配置过程中 I P数据报的目的地址,主机可能还不知道它所在网络的网络掩码,甚至连它的 I P地址也不知道。路由器都不转发目的地址为受限的广播地址的数据报,这样的数据报仅出现在本地网络中
  3. 指向网络的广播地址: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类为多播)
  4. 指向所有子网的广播:指向所有子网的广播也需要了解目的网络的子网掩码,以便与指向网络的广播地址区分开。指向所有子网的广播地址的子网号及主机号为全 1

对象:无限制,服务器、客户端均可接受和发送,因此实现的代码可一致,但是用户要求在同一子网,并且端口一样,因为端口号需一致,所以该实验就无法在一个虚拟机上进行,要多开一个虚拟机。

广播的应用

  1. ARP数据报:获取目标IP主机的MAC地址。
  2. DHCP协议:将尚未分配IP地址的主机,通过向连接的子网内DHCP发送请求,获取IP号。
  3. 还有其他的一些服务:如路由进程

广播的发送与接收过程

广播的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地址中。
在这里插入图片描述

与广播的区别

  1. 发送端的数据帧不同
    广播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
  2. 接收客户,广播的用户需要在同一子网,组播只需要用户加入到组播组中

IGMP协议

IGMP提供了在转发组播数据包到目的地的最后阶段所需的信息,实现如下双向的功能:

  1. 主机通过IGMP通知路由器希望接收或离开某个特定组播组的信息。
  2. 路由器通过IGMP周期性地查询局域网内的组播组成员是否处于活动状态,实现所连网段组成员关系的收集与维护。

用户如何加入到组播组

组播分两部分:组播数据流(上面的数据报)和组播控制流

组播控制流分三种报文:report(join)、leave、iquery,其中report和leave为用户设置的加入与离开,而iquery为路由器主动发出,无需设置

通过setsockopt修改socket套字节选项。发送控制报文
组播用户设置:

  1. IP_MULTICAST_LOOP:是否支持本机上的回环,即报文是否发一份给本机
  2. IP_ADD_MEMBERSHIP:加入组播组
  3. IP_DROP_MEMBERSHIP:离开组播组

多播编程实现

将所有需要接收的用户加入到组播组中,发送端可以不加入到组播组中,组播地址需在上方图表所示范围,所有加入到组播组的用户均可发送消息到组播组,在组播组的用户均能接收。

  1. 发送端(一般为服务器):发送的消息需对socket的IP段添加组播地址,还有固定组播的端口号。
  2. 接收端:必须加入到组播组中,并且接收消息的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;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值