《TCP IP网络编程》第十四章
第 14 章 多播与广播
14.1 多播
多播(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 次。但是此时如果用多播网络传输文件,则只需要发送一次。这时由 1000 台主机构成的网络中的路由器负责复制文件并传递到主机。就因为这种特性,多播主要用于「多媒体数据实时传输」。
另外,理论上可以完成多播通信,但是不少路由器并不支持多播,或即便支持也因网络拥堵问题故意阻断多播。因此,为了在不支持多播的路由器中完成多播通信,也会使用隧道(Tunneling)技术。
路由(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(join_adr);
...
下面是 ip_mreq 结构体的定义:
struct ip_mreq
{struct in_addr imr_multiaddr; //写入加入组的IP地址struct in_addr imr_interface; //加入该组的套接字所属主机的IP地址
};
实现多播 Sender 和 Receiver:
多播中用「发送者」(以下称为 Sender) 和「接收者」(以下称为 Receiver)替代服务器端和客户端。顾名思义,此处的 Sender 是多播数据的发送主体,Receiver 是需要多播组加入过程的数据接收主体。下面是示例,示例的运行场景如下:
- Sender : 向 AAA 组广播(Broadcasting)文件中保存的新闻信息
- Receiver : 接收传递到 AAA 组的新闻信息。
下面是示例代码:
news_sender:
#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_handling(char *message);int main(int argc, char *argv[])
{int send_sock;struct sockaddr_in mul_adr;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_DGRAM, 0); //创建 UDP 套接字memset(&mul_adr, 0, sizeof(mul_adr));mul_adr.sin_family = AF_INET;mul_adr.sin_addr.s_addr = inet_addr(argv[1]); //必须将IP地址设置为多播地址mul_adr.sin_port = htons(atoi(argv[2]));//指定套接字中 TTL 的信息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)) //如果文件没结束就返回0{fgets(buf, BUF_SIZE, fp);sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr *)&mul_adr, sizeof(mul_adr));sleep(2);}fclose(fp);close(send_sock);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}
news_receiver:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#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 adr;struct ip_mreq join_adr;if (argc != 3){printf("Usage : %s <GroupIP> <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[2]));if (bind(recv_sock, (struct sockaddr *)&adr, sizeof(adr)) == -1)error_handling("bind() error");//初始化结构体join_adr.imr_multiaddr.s_addr = inet_addr(argv[1]); //多播组地址join_adr.imr_interface.s_addr = htonl(INADDR_ANY); //待加入的IP地址//利用套接字选项 IP_ADD_MEMBERSHIP 加入多播组,完成了接受指定的多播组数据的所有准备setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&join_adr, sizeof(join_adr));while (1){//通过 recvfrom 函数接受多播数据。如果不需要知道传输数据的主机地址信息,可以向recvfrom函数的第5 6参数分别传入 NULL 0str_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)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}
运行结果:
通过结果可以看出,使用 sender 多播信息,通过 receiver 接收广播,如果延迟运行 receiver 将无法接受之前发送的信息。
14.2 广播
广播(Broadcast)在「一次性向多个主机发送数据」这一点上与多播类似,但传输数据的范围有区别。多播即使在跨越不同网络的情况下,只要加入多播组就能接受数据。相反,广播只能向同一网络中的主机传输数据。
广播的理解和实现方法:
广播是向同一网络中的所有主机传输数据的方法。与多播相同,广播也是通过 UDP 来完成的。根据传输数据时使用的IP地址形式,广播分为以下两种:
- 直接广播(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 网络中所有主机。
数据通信中使用的IP地址是与 UDP 示例的唯一区别。默认生成的套接字会阻止广播,因此,只需通过如下代码更改默认设置:
int send_sock;
int bcast;
...
send_sock=socket(PF_INET,SOCK_DGRAM,0);
...
setsockopt(send_sock,SOL_SOCKET,SO_BROADCAST,(void*)&bcast,sizeof(bcast));
...
实现广播数据的 Sender 和 Receiver:
下面是广播数据的 Sender 代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <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 <GroupIP> <PORT>\n", argv[0]);exit(1);}send_sock = socket(PF_INET, SOCK_DGRAM, 0); //创建 UDP 套接字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)) //如果文件没结束就返回0{fgets(buf, BUF_SIZE, fp);sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr *)&broad_adr, sizeof(broad_adr));sleep(2);}fclose(fp);close(send_sock);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}
ps:
sendto()
函数用于在无连接的数据报套接字(如UDP套接字)中发送数据,它不会进行连接的建立和断开操作,因此每次发送数据时都需要指定目标地址。这使得UDP套接字适用于一对多或多对多的通信场景,例如广播和组播。while (!feof(fp))
循环读取文件,直到文件结束。feof()
函数用于检查是否已到达文件末尾。
下面是Receiver代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#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 adr; // 地址结构体,用于存储服务器绑定的地址信息// 检查命令行参数,确保指定了端口号if (argc != 2){printf("Usage : %s <PORT>\n", argv[0]);exit(1);}// 创建一个UDP套接字recv_sock = socket(PF_INET, SOCK_DGRAM, 0);// 初始化地址结构体,绑定服务器的IP地址和端口号memset(&adr, 0, sizeof(adr));adr.sin_family = AF_INET; // 使用IPv4地址族adr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY表示接受任意IP地址发送的数据adr.sin_port = htons(atoi(argv[1])); // 从命令行参数获取端口号,并转换为网络字节序// 绑定套接字和地址信息if (bind(recv_sock, (struct sockaddr *)&adr, sizeof(adr)) == -1)error_handling("bind() error");while (1){// 通过recvfrom函数接收数据。如果不需要知道传输数据的主机地址信息,可以向recvfrom函数的第5、6参数传入NULL和0str_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)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}
运行结果:
习题 :
1、TTL 的含义是什么?请从路由器的角度说明较大的 TTL 值与较小的 TTL 值之间的区别及问题。
TTL 是决定「数据包传递距离」的主要因素。TTL 每经过一个路由器就减一。TTL 变为 0 时,数据包就无法再被传递,只能销毁。因此,TTL设置过大会影响网络流量。当然,设置过小无法传递到目标。
2、多播与广播的异同点是什么?请从数据通信的角度进行说明。
在「一次性向多个主机发送数据」这一点上与多播类似,但传输的数据范围有区别。多播即使在跨越不同网络的情况下,只要加入多播组就能接受数据。相反,广播只能向同一网络中的主机传输数据。
3、多播也对网络流量有利,请比较 TCP 交换方式解释其原因。
TCP 是必须建立一对一的连接,如果要向1000个主机发送文件,就得传递1000次。但是此时用多播方式传输数据,就只需要发送一次。
相关文章:

《TCP IP网络编程》第十四章
第 14 章 多播与广播 14.1 多播 多播(Multicast)方式的数据传输是基于 UDP 完成的。因此 ,与 UDP 服务器端/客户端的实现方式非常接近。区别在于,UDP 数据传输以单一目标进行,而多播数据同时传递到加入(注…...

网络基础-认识每层的设备和每层的特点用途
目录 网络层次常见设备各层介绍数据链路层网络层传输层应用层 网络层次 常见设备 各层介绍 数据链路层 有了MAC地址。数据链路层工作在局域网中的,以帧为单位进行传输和处理数据。 网络层 网络层有了IP。不同的网络通过路由器连接成为互联网 路由器的功能: …...
【Linux操作系统】深入解析Linux定时任务调度机制-cronat指令
在Linux操作系统中,定时任务调度是一项重要的功能,它可以让用户在指定的时间或周期性地执行特定的任务。这种机制使得用户能够自动化地执行一些重复性工作,提高工作效率。本文将详细介绍Linux定时任务调度的原理、常用指令和代码示例…...

动手学深度学习(一)预备知识
目录 一、数据操作 1. N维数组样例 2. 访问元素 3. 基础函数 (1) 创建一个行向量 (2)通过张量的shape属性来访问张量的形状和元素总数 (3)reshape()函数 (4)创建全0、全1、…...

item_get-KS-获取商品详情
一、接口参数说明: item_get-根据ID取商品详情 ,点击更多API调试,请移步注册API账号点击获取测试key和secret 公共参数 请求地址: https://api-gw.onebound.cn/ks/item_get 名称类型必须描述keyString是调用key(http://o0b.cn/…...
[华为OD] 最小传输时延(dijkstra算法)
明天就要面试了我也太紧张了吧 但是终于找到了一个比较好理解的dijkstra的python解法,让我快点把它背下来!!!! 文章目录 题目dijkstra算法的python实现python解答dfs解法dijkstra解法 题目 先把题目放出来 某通信网络…...

问道管理:总资产大于总市值好吗?
在财政领域,总财物和总市值是两个非常重要的指标。总财物是指公司所有的财物,包括固定财物、流动财物、无形财物等,而总市值则是指公司股票在商场上的总价值。当总财物大于总市值时,这是否是一个好的信号呢?咱们将从多…...

IBM Spectrum LSF (“LSF“ ,简称为负载共享设施) 用户案例
IBM Spectrum LSF (“LSF” ,简称为负载共享设施) 用户案例 IBM Spectrum LSF (“LSF” ,简称为负载共享设施) 软件是业界领先的企业级软件。 LSF 在现有异构 IT 资源之间分配工作,以创建共享,可扩展且容错的基础架构,…...

Pytorch深度学习-----神经网络之非线性激活的使用(ReLu、Sigmoid)
系列文章目录 PyTorch深度学习——Anaconda和PyTorch安装 Pytorch深度学习-----数据模块Dataset类 Pytorch深度学习------TensorBoard的使用 Pytorch深度学习------Torchvision中Transforms的使用(ToTensor,Normalize,Resize ,Co…...

Gis入门,使用起止点和两个控制点生成三阶贝塞尔曲线(共四个控制点,线段转曲线)
前言 本章讲解如何在gis地图中使用起止点和两个控制点(总共四个控制点)生成三阶贝塞尔曲线。 二阶贝塞尔曲线请参考上一章《Gis入门,如何根据起止点和一个控制点计算二阶贝塞尔曲线(共三个控制点)》 贝塞尔曲线(Bezier curve)介绍 贝塞尔曲线(Bezier curve)是一种…...

Web-7-深入理解Cookie与Session:实现用户跟踪和数据存储
深入理解Cookie与Session:实现用户跟踪和数据存储 今日目标 1.掌握客户端会话跟踪技术Cookie 2.掌握服务端会话跟踪技术Sesssion 1.会话跟踪技术介绍 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断…...
Springboot设置Https
1、修改配置文件application.yml,并将*.jks放到resource目录下。 server:port: 8080ssl:key-store: classpath:*.jkskey-store-password: *key-store-type: JKSenabled: truekey-alias: boe.com.cn2、添加http转https的配置 Configuration public class TomcatCon…...

Windows 使用 Linux 子系统,轻轻松松安装多个linux
Windows Subsystem for Linux WSL 简称WSL,是一个在Windows 10\11上能够运行原生Linux二进制可执行文件(ELF格式)的兼容层。它是由微软与Canonical公司合作开发,其目标是使纯正的Ubuntu、Debian等映像能下载和解压到用户的本地计算机&#…...

中级课程——弱口令(认证崩溃)
文章目录 什么是弱口令密码生成器分类暴力破解万能密码测试环境工具 什么是弱口令 密码生成器 分类 暴力破解 万能密码 or true --测试环境 工具 九头蛇,超级弱口令爆破工具,bp,...

web自动化测试进阶篇05 ——— 界面交互场景测试
😏作者简介:博主是一位测试管理者,同时也是一名对外企业兼职讲师。 📡主页地址:【Austin_zhai】 🙆目的与景愿:旨在于能帮助更多的测试行业人员提升软硬技能,分享行业相关最新信息。…...

NICE-SLAM: Neural Implicit Scalable Encoding for SLAM论文阅读
论文信息 标题:NICE-SLAM: Neural Implicit Scalable Encoding for SLAM 作者:Zihan Zhu, Songyou Peng,Viktor Larsson — Zhejiang University 来源:CVPR 代码:https://pengsongyou.github.io/nice-slam…...

cmake 配置Visual studio的调试命令
配置代码如截图: set_property(TARGET ${TARGET_NAME} PROPERTY VS_DEBUGGER_COMMAND "./consoleTest.exe") set_property(TARGET ${TARGET_NAME} PROPERTY VS_DEBUGGER_COMMAND_ARGUMENTS "./config/labelDriver.cfg") set_propert…...

MPDIoU: A Loss for Efficient and Accurate Bounding BoxRegression--论文学习笔记
超越GIoU/DIoU/CIoU/EIoU MPDIoU让YOLOv7和YOLACT双双涨点 目标检测上的指标对比: 论文地址: [2307.07662] MPDIoU: A Loss for Efficient and Accurate Bounding Box Regression (arxiv.org) 摘要 边界框回归(Bounding Box Regression&am…...

【Uniapp 的APP热更新】
Uniapp 的APP热更新功能依赖于其打包工具 HBuilder,具体步骤如下: 1. 在 HBuilder 中构建并打包出应用程序 具体步骤: 1.点击发行,点击制作wgt包 2.根据需求修改文件储存路径和其他配置,点击确定 3.等待打包完成&a…...
MySQL主从复制配置
Mysql的主从复制至少是需要两个Mysql的服务,当然Mysql的服务是可以分布在不同的服务器上,也可以在一台服务器上启动多个服务。 (1)首先确保主从服务器上的Mysql版本相同 (2)在主服务器上,创建一个充许从数据库来访问的用户slave,密码为:123456 ,然后使用REPLICATION SLAV…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...

使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?
在工业自动化持续演进的今天,通信网络的角色正变得愈发关键。 2025年6月6日,为期三天的华南国际工业博览会在深圳国际会展中心(宝安)圆满落幕。作为国内工业通信领域的技术型企业,光路科技(Fiberroad&…...