百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Linux网络应用开发–-单播、广播和组播

wxin55 2024-12-26 17:13 9 浏览 0 评论

一、概念:

1. 单播、广播和组播三者都是使用UDP协议进行数据传输的。可以这样理解, 客户端和服务器相互通信前不需要建立连接,直接向指定的对方ip地址和端口号发送数据, 无论对方是否存在,是否在线,以及是否收到数据,均不用关心;

2. 三者区别:

---单播:用于两个主机之间的端对端通信;

---广播:用于一个主机对整个局域网上所有主机上的数据通信,网络中的所有主机都会接收同一份数据副本;

---组播:用于对一组特定的主机进行通信,且这个特定的组内主机可以动态添加和删除多台主机,组内所有主机收到相同一份数据副本;

本文将主要讨论一对多的服务:广播(broadcast)和多播(multicast),单播通信见另一篇UDP和TCP网络通信的文章;

二、广播通信:

1. 广播的用途:

1) 地址解析协议(ARP,寻找IP地址对应的MAC地址)

? 2) 动态主机配置协议(DHCP,向路由器申请IP地址)

? 3) 网络时间协议(NTP)

2. 广播的特点:

1) 处于同一子网的所有主机都必须处理数据;

2) UDP数据包会沿协议栈向上一直到UDP层

3) 运行音视频等较高速率工作的应用,会带来大负载

4) 局限于局域网内使用,不可用于广域网;

3. 广播地址:

先说明下主机IP的组成:网络ID + 主机ID

---网络ID表示由子网掩码中1覆盖的连续位

---主机ID表示由子网掩码中0覆盖的连续位

1) 定向广播地址:主机ID全1

对于192.168.1.0/24,其定向广播地址为192.168.1.255(所有主机必须无条件接收)

2) 受限广播地址:255.255.255.255;

3) 广播信息是不会被路由器转发,即只能在同一网段内通信

4)端口号可以自定义,但发送和接收端应保持一致才能收到;

4. 调用接口和流程:

广播通信是UDP通信的一种特殊情况,所以从socket创建到数据的收发与UDP单播通信是一样的,主要区别在setsockopt函数的设置;

setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(int));

具体参数如下:

---广播包发送流程如下:

1) 创建UDP套接字;socket(AF_INET, SOCK_DGRAM, 0)

2) 填充广播信息结构体;struct sockaddr_in

3) 设置套接字选项允许发送广播包;setsockopt(, ,SO_BROADCAST, ,)

4) 发送数据包;sendto( )


---广播包接收流程如下:

1) 创建UDP套接字;socket(AF_INET, SOCK_DGRAM, 0)

2) 填充广播信息结构体;struct sockaddr_in

3) 绑定地址和端口;bind( )

4) 接收数据包;recvfrom( )

5. 例子程序:


--------------广播发送: broadcast_send.c-----------------------------------

	#include <stdio.h>
	#include <sys/socket.h>
	#include <netinet/in.h>
	#include <string.h>
	#include <arpa/inet.h>

	int main(int argc, char const *argv[])
	{
		//创建一个udp套接字
		int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

		if(sockfd < 0)
		{
			perror("sockfd");
			return -1;
		}
		
		//设置套接字允许广播
		int yes = 1;
		int k = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(int));
		if(k < 0)
		{
			perror("stsockopt");
			return -1;
		}
		
		//定义地址结构存放广播地址信息
		struct sockaddr_in dst_addr;
		bzero(&dst_addr, sizeof(dst_addr));
		dst_addr.sin_family = AF_INET;
		dst_addr.sin_port = htons(9000);
		dst_addr.sin_addr.s_addr= inet_addr("192.168.1.255"); //广播地址
		
		//连接发送3次广播数据
		for(int i = 0; i < 3; i++)
		{
			sendto(sockfd, "this is broadcast", strlen("this is broadcast"), \
				  0, (struct sockaddr *)&dst_addr, sizeof(dst_addr));
			sleep(1);
		}
		
		close(sockfd);
		return 0;
	}

--------------广播接收:broadcast_receive.c-----------------------------------

		#include <stdio.h>
		#include <sys/socket.h>
		#include <netinet/in.h>
		#include <string.h>
		#include <arpa/inet.h>

		int main(int argc, char const *argv[])
		{
			int sockfd = socket(AF_INET,SOCK_DGRAM,0);//创建套接字
			
			if(sockfd<0)
			{
				perror("sockfd");
				return -1;
			}
			
			struct sockaddr_in server,client; 
			server.sin_family=AF_INET;
			server.sin_port=htons(9000);
			server.sin_addr.s_addr=inet_addr("0");
			int n=sizeof(server);

			int ret = bind(sockfd,(struct sockaddr *)&server,n);//绑定套接字
			if(ret<0)
			{
				perror("bind");
			}   

			char buf[64]={0};
			int m=sizeof(client);
			while(1)
			{
				/*接收广播信息*/
				recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&client,&m);
				
				/*打印发送端的主机信息*/
				printf("clinet IP=%s client port=%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
				
				/*打印接收到的的广播信息*/
				printf("message:%s\n",buf);	
				memset(buf,0,sizeof(buf));
			}
			close(sockfd);
		 
			return 0;
		}

三、组播通信:

1. 组播的用途:

组播通信也是UDP协议的一种特殊情况, 不过有专门的组播地址。 多播是Linux系统中实现网络通信的重要方式,可以实现将消息发送到网络上的所有设备或者特定的一组设备。 这种通信方式在网络管理、视频会议等应用场景中都有着广泛的应用,如当前广泛使用的QQ,微信等均采用此技术;

2. 组播地址和特点:

1) 组播的实现依赖于D类IP地址,范围从224.0.0.0到239.255.255.255。这些地址被划分为局部链接组播地址、预留组播地址和管理权限组播地址。

具体分配如下:

224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;

224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet;

224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效;

239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。

2)主机可以向路由器请求加入或退出某个组播组,然后路由器和交换机会有选择地复制并传输数据,只将数据传输给组内的主机。

3)组播技术特点包括:

a. 带宽效率:组播允许数据在同一分组的主机之间共享,节省了网络带宽。

b. 服务器负载:由于组播协议由接收者的需求来确定是否进行数据流的转发,所以服务器端的带宽是常量,与客户端的数量无关。

c. 广域网支持:与广播不同,组播可以在广域网,如Internet上进行。

4. 调用接口和流程:

特别重要的设置函数:

int setsockopt( int sockfd, int level,int optname,const void *optval, socklen t optlen );

---发送端调用流程如下:

1) 创建套接字;

2) 初始化多播地址;

3) 设置协议族为AF;

4) 设置多播IP地址;

5) 设置多播端口;

6) 向多播地址发送数据;

8) 关闭套接字;


---接收端调用流程如下:

1) 创建套接字;

2) 初始化本地地址;

3) 绑定socket;

4) 设置回环许可;

5) 加入播播组;

6) 开始循环,接收数据;

7) 离开组播组;

8) 关闭套接字;

说明:组播的几个重要设置

---设置回环地址:

//set the loopback case

setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, ...);

---打开组播设置:

setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, ..., ...);

---加入组播组:

setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, ..., ...);


---离开组播组:

setsockopt(sock_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, ..., ...);

---设置数据包的

setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_HOPS, ..., ...);


5. 例子程序:

注意点:

1)必须用setsockopt函数开通套接字的组播权限。发送端使用IPPROTO_IP和IP_MULTICAST_IF

2)函数if_nametoindex的作用是通过网卡的名字,取得网卡的mac地址。

		-------------组播发送:multicast_send.c-----------------------------------
		#include <sys/types.h> 
		#include <sys/socket.h>
		#include <arpa/inet.h>
		#include <stdlib.h>
		#include <unistd.h>
		#include <stdio.h>
		#include <string.h>
		#include <net/if.h>
		
		#define MULTICAST_IP	234.5.6.7

		int main(int agrc, char** argv){

		  /*1. */	
		  int fd = socket(AF_INET, SOCK_DGRAM, 0);

		  int port = atoi(argv[1]);
		  struct sockaddr_in addr;
		  addr.sin_family = AF_INET;
		  addr.sin_port = htons(port);
		  addr.sin_addr.s_addr = htonl(INADDR_ANY);
		  
		  bind(fd, (struct sockaddr*)&addr, sizeof(addr));
		 
		  struct ip_mreqn n;
		  inet_pton(AF_INET, MULTICAST_IP, &n.imr_multiaddr.s_addr);//组播地址
		  inet_pton(AF_INET, "0.0.0.0", &n.imr_address.s_addr);//本机地址
		  n.imr_ifindex = if_nametoindex("enp0s3");//本机的通信用的网卡的物理地址,可以用ifconfig查看
		  
		  int ret =  setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF,
					&n, sizeof(n));
		  
		  struct sockaddr_in client;
		  memset(&client, 0, sizeof(client));
		  client.sin_family = AF_INET;
		  client.sin_port = htons(6666);
		  inet_pton(AF_INET, "239.0.0.10", &client.sin_addr.s_addr);
		  
		  int cnt = 0;
		  while(1){
			char buf[64] = {0};
			sprintf(buf, "count=%d", cnt++);
			int ret = sendto(fd, buf, sizeof buf, 0, (struct sockaddr*)&client, sizeof(client));
			if(ret == -1){
			  perror("sendto -1");
			}
			sleep(1);
		  }		  
		}


-------------组播接收端 :multicast_receive.c-----------------------------------

注意点:

1)必须用setsockopt函数把接收端的套接字加入到组播的组里。接收端使用IPPROTO_IP和IP_ADD_MEMBERSHIP

2)由于组播的时候,必须指定接收端的端口号,所以接收端必须调用bind函数,显示的指定自己用的端口号

		#include <sys/types.h> 
		#include <sys/socket.h>
		#include <arpa/inet.h>
		#include <stdlib.h>
		#include <unistd.h>
		#include <stdio.h>
		#include <string.h>
		#include <net/if.h>
		
		#define MULTICAST_IP	234.5.6.7

		int main(int agrc, char** argv){

		  int fd = socket(AF_INET, SOCK_DGRAM, 0);

		  struct sockaddr_in addr;
		  memset(&addr, 0, sizeof(addr));
		  addr.sin_family = AF_INET;
		  addr.sin_port = htons(6666);
		  //addr.sin_addr.s_addr = htons(INADDR_ANY);
		  inet_pton(AF_INET, "0.0.0.0", &addr.sin_addr.s_addr);
		  socklen_t len = sizeof(addr);

		  bind(fd, (struct sockaddr*)&addr, sizeof(addr));

		  struct ip_mreqn n;
		  inet_pton(AF_INET, MULTICAST_IP, &n.imr_multiaddr.s_addr);
		  inet_pton(AF_INET, "0.0.0.0", &n.imr_address.s_addr);
		  n.imr_ifindex = if_nametoindex("enp0s3");
		  
		  int ret =  setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
					&n, sizeof(n));  
		  
		  while(1){
			char buf[64] = {0};
			int ret = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
			write(STDOUT_FILENO, buf, ret);
			sleep(1);
		  }
	}

相关推荐

ES6中 Promise的使用场景?(es6promise用法例子)

一、介绍Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码doSomething(f...

JavaScript 对 Promise 并发的处理方法

Promise对象代表一个未来的值,它有三种状态:pending待定,这是Promise的初始状态,它可能成功,也可能失败,前途未卜fulfilled已完成,这是一种成功的状态,此时可以获取...

Promise的九大方法(promise的实例方法)

1、promise.resolv静态方法Promise.resolve(value)可以认为是newPromise方法的语法糖,比如Promise.resolve(42)可以认为是以下代码的语...

360前端一面~面试题解析(360前端开发面试题)

1.组件库按需加载怎么做的,具体打包配了什么-按需加载实现:借助打包工具(如Webpack的require.context或ES模块动态导入),在使用组件时才引入对应的代码。例如在V...

前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?

Promise的finally方法是一个非常有用的工具,它无论Promise是成功(fulfilled)还是失败(rejected)都会执行,且不改变Promise的最终结果。它的实现原...

最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式

看了全网手写Promise的,大部分对于新手还是比较难理解的,其中几个比较难的点:状态还未改变时通过发布订阅模式去收集事件实例化的时候通过调用构造函数里传出来的方法去修改类里面的状态,这个叫Re...

前端分享-Promise可以中途取消啦(promise可以取消吗)

传统Promise就像一台需要手动组装的设备,每次使用都要重新接线。而Promise.withResolvers的出现,相当于给开发者发了一个智能遥控器,可以随时随地控制异步操作。它解决了三大...

手写 Promise(手写输入法 中文)

前言都2020年了,Promise大家肯定都在用了,但是估计很多人对其原理还是一知半解,今天就让我们一起实现一个符合PromiseA+规范的Promise。附PromiseA+规范地址...

什么是 Promise.allSettled()!新手老手都要会?

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的pr...

前端面试-关于Promise解析与高频面试题示范

Promise是啥,直接上图:Promise就是处理异步函数的API,它可以包裹一个异步函数,在异步函数完成时抛出完成状态,让代码结束远古时无限回掉的窘境。配合async/await语法糖,可...

宇宙厂:为什么前端离不开 Promise.withResolvers() ?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.为什么需要Promise.with...

Promise 新增了一个超实用的 API!

在JavaScript的世界里,Promise一直是处理异步操作的神器。而现在,随着ES2025的发布,Promise又迎来了一个超实用的新成员——Promise.try()!这个新方法简...

一次搞懂 Promise 异步处理(promise 异步顺序执行)

PromisePromise就像这个词的表面意识一样,表示一种承诺、许诺,会在后面给出一个结果,成功或者失败。现在已经成为了主流的异步编程的操作方式,写进了标准里面。状态Promise有且仅有...

Promise 核心机制详解(promise机制的实现原理)

一、Promise的核心状态机Promise本质上是一个状态机,其行为由内部状态严格管控。每个Promise实例在创建时处于Pending(等待)状态,此时异步操作尚未完成。当异步操作成功...

javascript——Promise(js实现promise)

1.PromiseES6开始支持,Promise对象用于一个异步操作的最终完成(包括成功和失败)及结果值的表示。简单说就是处理异步请求的。之所以叫Promise,就是我承诺,如果成功则怎么处理,失败怎...

取消回复欢迎 发表评论: