如何在Linux c/c++ 进行多播(组播)编程

news2025/1/10 17:05:24

第一章: 前言

多播技术,也被称为“组播”,是一种网络通信机制,它允许一个节点(发送者)向一组特定的节点(接收者)发送信息。这种方式在网络编程中非常有用,因为它可以大大提高效率和性能,同时减少网络带宽的使用。

在单播通信中,信息从一个节点发送到另一个节点,而在广播通信中,信息从一个节点发送到网络中的所有节点。多播则介于这两者之间,信息从一个节点发送到一组特定的节点。这种方式特别适合于需要向一组特定的主机发送信息的场景,例如在线视频会议或实时数据共享。

多播的实现依赖于D类IP地址,范围从224.0.0.0到239.255.255.255。这些地址被划分为局部链接多播地址、预留多播地址和管理权限多播地址。主机可以向路由器请求加入或退出某个多播组,然后路由器和交换机会有选择地复制并传输数据,只将数据传输给组内的主机。

多播技术的优点包括:

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

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

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

然而,多播技术也有一些缺点:

  1. 无纠错机制:多播与单播相比没有纠错机制,当发生错误的时候难以弥补,但是可以在应用层来实现此种功能。

  2. 网络支持:多播的网络支持存在缺陷,需要路由器及网络协议栈的支持。

总的来说,多播技术是一种强大的网络通信机制,它在许多场景中都非常有用,如网上视频、网上会议等。然而,要有效地使用多播,需要理解其工作原理,并确保网络设备和协议栈正确地支持多播。

第二章: 广域网的多播

多播是一种网络通信机制,它允许一个节点(发送者)向一组节点(接收者)发送信息。这种方式在网络编程中非常有用,因为它可以大大提高效率和性能。

在IP网络中,多播地址被定义为D类IP地址,范围从224.0.0.0到239.255.255.255。这些地址被划分为三类:

  1. 局部链接多播地址:这个范围是从224.0.0.0到224.0.0.255。这些地址主要用于网络设备之间的本地通信,例如路由器之间的通信。这些地址的数据包不会被路由器转发到其他网络。

  2. 预留多播地址:这个范围是从224.0.1.0到238.255.255.255。这些地址可以在全球范围内使用,例如在Internet上。这意味着,如果你的应用程序需要向全球的多个节点发送信息,你可以使用这个范围内的地址。

  3. 管理权限多播地址:这个范围是从239.0.0.0到239.255.255.255。这些地址类似于私有IP地址,只能在特定的组织或企业内部使用。这些地址的数据包不能在Internet上路由,因此可以用来限制多播的范围。

在使用多播地址时,你需要注意一些事情。首先,你需要确保你的网络设备(如路由器和交换机)支持多播,并且已经正确配置。其次,你需要选择正确的多播地址,以确保你的信息能够正确地发送到目标节点。

希望这个解释能帮助你更深入地理解广域网的多播和多播地址。如果你还有其他问题,欢迎随时向我提问。

第三章: 多播的编程

多播是一种在网络中发送信息的方式,它允许一个节点向一组节点发送信息,而不是单独向每个节点发送。这种方式在网络编程中非常有用,因为它可以大大提高效率和性能。

在多播编程中,我们使用一些特殊的套接字选项来控制多播行为。这些选项通常在IP层设置,因为多播是在IP层实现的。

  1. IP_MULTICAST_TTL:这个选项用于设置多播数据包的生存时间(TTL)。TTL是一个在0到255之间的值,它决定了数据包可以通过多少个路由器。每当数据包通过一个路由器,其TTL就会减1,当TTL达到0时,数据包就会被丢弃。这个选项可以防止多播数据包在网络中无限制地传播。

  2. IP_MULTICAST_IF:这个选项用于设置默认的多播接口。多播数据包将从这个接口发送,其他接口将忽略这些数据包。

  3. IP_MULTICAST_LOOP:这个选项用于控制是否允许多播数据包在本地回环。如果设置为1,数据包将在本地回环;如果设置为0,数据包将不会在本地回环。

  4. IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP:这两个选项用于控制节点是否加入或离开一个多播组。当一个节点加入一个多播组后,它就可以接收到发送到该组的所有数据包。

在编写多播程序时,我们通常遵循以下步骤:

  1. 创建一个套接字:我们首先需要创建一个套接字来发送和接收数据。

  2. 设置多播参数:然后,我们需要设置多播的参数,如TTL和本地回环。

  3. 加入多播组:接下来,我们需要将节点加入到一个多播组中。

  4. 发送和接收数据:一旦节点加入了一个多播组,它就可以开始发送和接收数据了。

  5. 离开多播组:最后,当节点不再需要接收多播数据时,它可以从多播组中离开。

以下是一个使用C++编写的多播编程示例。这个示例中,我们将创建一个发送者和一个接收者,发送者将向一个多播组发送数据,接收者将接收这些数据。

首先,我们创建一个发送者:

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

int main() {
    // 创建套接字
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        std::cerr << "Error creating socket" << std::endl;
        return -1;
    }

    // 设置多播TTL参数
    unsigned char ttl = 1;
    if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) {
        std::cerr << "Error setting multicast TTL" << std::endl;
        return -1;
    }

    // 设置目标地址
    sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("239.0.0.1"); // 多播地址
    addr.sin_port = htons(12345); // 目标端口

    // 发送数据
    std::string msg = "Hello, Multicast!";
    if (sendto(sock, msg.c_str(), msg.size(), 0, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        std::cerr << "Error sending data" << std::endl;
        return -1;
    }

    std::cout << "Data sent to multicast group" << std::endl;

    return 0;
}

然后,我们创建一个接收者:

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() {
    // 创建套接字
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        std::cerr << "Error creating socket" << std::endl;
        return -1;
    }

    // 绑定到本地地址和端口
    sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY); // 任意本地地址
    addr.sin_port = htons(12345); // 目标端口

    if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        std::cerr << "Error binding socket" << std::endl;
        return -1;
    }

    // 加入多播组
    ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr("239.0.0.1"); // 多播地址
    mreq.imr_interface.s_addr = htonl(INADDR_ANY); // 任意网络接口

    if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
        std::cerr << "Error joining multicast group" << std::endl;
        return -1;
    }

    // 接收数据
    char buf[1024];
    memset(buf, 0, sizeof(buf));
    if (recv(sock, buf, sizeof(buf), 0) < 0) {
        std::cerr << "Error receiving data" << std::endl;
        return -1;
    }

    std::cout << "Received data: " << buf << std::endl;

    // 离开多播组
    if (setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
        std::cerr << "Error leaving multicast group" << std::endl;
        return -1;
    }

    return 0;
}

第四章:深入理解Linux内核中的多播

在Linux内核中,多播功能是通过一系列精心设计的数据结构和相应的操作来实现的。本章将深入探讨这些数据结构,包括struct ip_mc_sockliststruct ip_mreqnstruct ip_sf_socklist,以及如何通过IP_ADD_MEMBERSHIPIP_DROP_MEMBERSHIP选项来操作这些数据结构,从而实现多播功能。

4.1 多播的核心数据结构:ip_mc_socklist

在Linux内核中,多播是通过struct ip_mc_socklist数据结构来实现的。这个数据结构连接了多播的各个方面,包括多播的TTL(Time To Live)、是否启用多播回环、多播设备序号、多播地址以及多播群组。

4.2 加入和离开多播组:IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP

在Linux中,我们可以通过IP_ADD_MEMBERSHIP选项来将一个本地IP地址加入到一个多播组。这个过程主要包括将用户数据复制到内核、检查多播IP地址的合法性、查找对应的网络接口、检查多播列表中是否已存在该多播地址,以及将该多播地址加入到列表中。

相反,我们可以通过IP_DROP_MEMBERSHIP选项来将一个本地IP地址从一个多播组中移除。这个过程主要包括将用户数据复制到内核、查找对应的网络接口、检查多播列表中是否已存在该多播地址,以及将该多播地址从源地址和多播列表中移除。

4.3 源过滤:ip_sf_socklist

在Linux内核中,我们可以通过struct ip_sf_socklist数据结构来实现源过滤。这个数据结构包含了一个源地址列表,以及列表中源地址的数量和列表的最大容量。我们可以通过这个数据结构来控制哪些源的多播数据报被接收,哪些源的多播数据报被排除。

通过深入理解这些数据结构和操作,我们可以更好地理解Linux内核中的多播功能,以及如何在我们的应用中使用多播。在下一章中,我们将探讨如何在实际应用中使用Linux的多播功能。

一个多播服务器端的示例

下面是一个改进后的多播服务器端的示例。这个示例程序将持续向多播IP地址"224.0.0.88"的8888端口发送数据"BROADCAST TEST DATA",每发送一次间隔5s。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define MCAST_PORT 8888
#define MCAST_ADDR "224.0.0.88"
#define MCAST_DATA "BROADCAST TEST DATA"
#define MCAST_INTERVAL 5

int main(void)
{
    int sockfd;
    struct sockaddr_in mcast_addr;

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1)
    {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 初始化多播地址
    memset(&mcast_addr, 0, sizeof(mcast_addr));
    mcast_addr.sin_family = AF_INET;
    mcast_addr.sin_addr.s_addr = inet_addr(MCAST_ADDR);
    mcast_addr.sin_port = htons(MCAST_PORT);

    // 循环发送多播数据
    while (1)
    {
        int n = sendto(sockfd, MCAST_DATA, sizeof(MCAST_DATA), 0, (struct sockaddr*)&mcast_addr, sizeof(mcast_addr));
        if (n == -1)
        {
            perror("sendto");
            exit(EXIT_FAILURE);
        }

        // 等待一段时间
        sleep(MCAST_INTERVAL);
    }

    close(sockfd);

    return 0;
}

这个程序的流程如下:

在这里插入图片描述

  1. 创建套接字。
  2. 初始化多播地址。
  3. 设置协议族为AF。
  4. 设置多播IP地址。
  5. 设置多播端口。
  6. 开始循环,向多播地址发送数据。
  7. 等待一段时间。
  8. 回到步骤6。

这个程序的改进之处在于,它更加简洁,更加注重错误处理,确保在发生错误时能够及时退出并给出错误信息。同时,它也更加注重资源管理,确保在程序结束时关闭套接字。

一个多播客户端的示例

下面是一个改进后的多播客户端的示例。这个示例程序将加入多播组"224.0.0.88",监听端口8888,接收并打印出从多播组收到的数据。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define MCAST_PORT 8888
#define MCAST_ADDR "224.0.0.88"
#define MCAST_INTERVAL 5
#define BUFF_SIZE 256

int main(void)
{
    int sockfd;
    struct sockaddr_in local_addr;
    struct ip_mreq mreq;
    char buff[BUFF_SIZE];
    int n;

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1)
    {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 初始化本地地址
    memset(&local_addr, 0, sizeof(local_addr));
    local_addr.sin_family = AF_INET;
    local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    local_addr.sin_port = htons(MCAST_PORT);

    // 绑定socket
    if (bind(sockfd, (struct sockaddr*)&local_addr, sizeof(local_addr)) == -1)
    {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    // 设置回环许可
    int loop = 1;
    if (setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) == -1)
    {
        perror("setsockopt: IP_MULTICAST_LOOP");
        exit(EXIT_FAILURE);
    }

    // 加入多播组
    mreq.imr_multiaddr.s_addr = inet_addr(MCAST_ADDR);
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);
    if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
    {
        perror("setsockopt: IP_ADD_MEMBERSHIP");
        exit(EXIT_FAILURE);
    }

    // 循环接收多播组的消息
    for (int times = 0; times < 5; times++)
    {
        socklen_t addr_len = sizeof(local_addr);
        memset(buff, 0, BUFF_SIZE);

        n = recvfrom(sockfd, buff, BUFF_SIZE, 0, (struct sockaddr*)&local_addr, &addr_len);
        if (n == -1)
        {
            perror("recvfrom");
            exit(EXIT_FAILURE);
        }

        // 打印信息
        printf("Recv %dst message from server: %s\n", times, buff);

        // 等待一段时间
        sleep(MCAST_INTERVAL);
    }

    // 退出多播组
    if (setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
    {
        perror("setsockopt: IP_DROP_MEMBERSHIP");
        exit(EXIT_FAILURE);
    }

    close(sockfd);

    return 0;
}

这个程序的流程如下:

在这里插入图片描述

  1. 创建套接字。
  2. 初始化本地地址。
  3. 绑定socket。
  4. 设置回环许可。
  5. 加入多播组。
  6. 开始循环,接收数据。
  7. 打印信息。
  8. 等待一段时间。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/605516.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Git、GitFlow协作 、Git commit规范、语义化版本

目录 一、概述 二、Git 2.1 安装与配置 2.2 基本指令操作 2.3 创建一个新的存储库 2.4 推送一个已有的文件夹 2.5 忽略临时文件 2.6 添加commit模板 2.7 冲突解决 二、GitFlow协作 三、Git Commit规范 四、语义化版本 为什么需要语义化版本号&#xff1f; 什么是…

线段树为什么可以开三倍空间

参考链接 四倍空间的原因如上图所示&#xff0c;但是实际操作时&#xff0c;我们可以直接开三倍空间也是可以的。 原因分析&#xff1a; 由于在分割区间时&#xff0c;我们计算mid使用下取整&#xff0c;所以左边区间大小大于等于右边区间大小&#xff0c;如果要实现上图中的树…

智能路由器开发之创建一个procd init脚本示例

智能路由器开发之创建一个procd init脚本示例 Procd init脚本默认提供了许多好用的功能&#xff0c;例如重启策略和能够从UCI系统中存储和读取配置。 设置 举个例子&#xff0c;假设我们想创建一个作为服务的Shell脚本&#xff0c;并且这个服务可以通过消息和超时时间进行配…

C语言---初识指针

1、指针是什么 指针是什么&#xff1f; 指针理解的2个要点&#xff1a; ​ 1、指针是内存中一个最小单元的编号&#xff0c;也就是地址。 ​ 2、平时口语中说的指针&#xff0c;通常指的是指针变量&#xff0c;是用来存放内存地址的变量 总结&#xff1a;指针就是地址&#xff…

Docker+Jenkins+Gitee自动化部署maven项目,加入Nexus镜像仓库(补充篇)

1.前言 Hello&#xff0c;各位小伙伴&#xff0c;大家好&#xff01;&#xff01;&#xff01; 在【DockerJenkinsGitee自动化部署maven项目】一文中&#xff0c;我们介绍了如何使用Jenkins来实现自动化部署maven项目&#xff0c;没读过的小伙伴可以去回顾一下&#xff0c;这…

【求 一个人去给多个人拜年 的最短路径】【枚举所有 拜访顺序】新年好

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

ChatGPT 70个插件小测全纪录

以下插件排序按照 ChatGPT all plugins 列表,评分基于国人使用场景。 1)Polarr:用于处理和编辑图片的工具 Polarr插件可以帮助用户进行各种图片编辑任务,包括调整亮度、对比度、饱和度,应用滤镜,裁剪图片,以及其他一些高级的图片处理功能。这个插件可以让ChatGPT更好地…

章节1:XXE漏洞-上

章节1&#xff1a;XXE漏洞-上 01 XML基础知识 XML eXtensible Markup Language 可扩展标记语言 XML用途 配置文件 交换数据 XML内容 XML格式要求 XML文档必须有根元素XML文档必须有关闭标签XML标签对大小写敏感XML元素必须被正确的嵌套XML属性必须加引号 XML格式校验 …

耗时 48小时整理了网络安全学习路线,非常详细!

前言 上次发的文章【都2023年了&#xff0c;还在问网络安全怎么入门】很多小伙伴在评论区回复不知道怎么学习&#xff0c;我也反思了一下&#xff0c;确实没写到学习方法和路线&#xff0c;所以这一期就出一一个怎么学习网络安全的学习路线和方法&#xff0c;觉得有用的话三连…

mvn 打包jar包。 Docker 部署 jar 包程序

默认你已经安装了jdk和maven 并且配置了环境变量. 这里贴出自己的环境配置(mac) # Maven3.6.3 export M2_HOME/Users/cc/maven3.6.3/apache-maven-3.6.3 export M2$M2_HOME/bin export PATH$M2:$PATH# java8 export JAVA_HOME/Library/Java/JavaVirtualMachines/jdk1.8.0_36…

表白墙的实现【前后端交互】

目录 一、Servlet API 详解 1. HttpServletRequest 1.1 HttpServletRequest 方法 1.2 getParameter 2.HttpServletResponse 2.1 HttpServletResponse 方法 2.2 代码示例: 设置状态码 2.3 代码示例: 重定向 二、表白墙 1.准备工作 2.约定前后端交互接口 2.1 接口一…

项目引入Spring Security的参考步骤token

后端&#xff1a; 1、在数据库中创建t_user用户表&#xff0c;参照建表SQL。 2、pom文件中引入Spring Security依赖、JWT依赖&#xff08;复制粘贴即可&#xff09; <!--security--> <dependency> <groupId>org.springfr…

全志V3S嵌入式驱动开发(pwm驱动)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 pwm驱动也是常见的一种驱动方式。常见的pwm&#xff0c;其实就是一组方波&#xff0c;方波中的高低电平之比称之为空占比。通过调节这个空占比&…

前端学习--ES6模块化与异步编程高级用法

一、ES6模块化 1.1 概念与规则 ES6 模块化规范是浏览器端与服务器端通用的模块化开发规范 ES6 模块化规范中定义&#xff1a; 每个 js 文件都是一个独立的模块导入其它模块成员使用 import 关键字向外共享模块成员使用 export 关键字 1.2 在node.js体验es6模块化 配置如下&…

【问题】常见问题解决方法

记录在项目运行中遇到的问题&#xff0c;和常用的软件安装包 文章目录 安装包下载第一章&#xff1a;运行C/C小白运行须知1.DevC运行&#xff08;最简单&#xff0c;推荐&#xff09;2.Visual Studio 运行3.VC运行 第二章&#xff1a;运行C#项目1.VS环境2.打开C#项目启动失败&a…

LiteDram仿真验证(二):仿真中,DDR3初始化问题

目录 前言一、讨论1、[init_done never goes to 1 in simulation #145](https://github.com/enjoy-digital/litedram/issues/145)2、[Add ECP5 support to standalone core generator #106](https://github.com/enjoy-digital/litedram/issues/106)3、[Help generating DDR3 Ve…

【unity插件】2d切割破坏插件-Smart Slicer 2D

文章目录 效果1.切割2.破坏3.创建源码使用1.导入插件2.摄像机3.新建地面4.新建切割刀5.新建切割食物6. 运行即可不同slicer Type的切割刀类型测试1.线性2.Complex3.点4.多边形5.explode6.创建效果 1.切割

华为OD机试之数列描述(Java源码)

数列描述 题目描述 有一个数列a[N] (N60)&#xff0c;从a[0]开始&#xff0c;每一项都是一个数字。数列中a[n1]都是a[n]的描述。其中a[0]1。规则如下&#xff1a; a[0]:1a[1]:11(含义&#xff1a;其前一项a[0]1是1个1&#xff0c;即“11”。表示a[0]从左到右&#xff0c;连续出…

Java简单实现短信验证登录(Session、Redis)

前端设计 <div class"login-form"><div style"display: flex; justify-content: space-between"><el-input style"width: 60%" placeholder"请输入手机号" v-model"form.phone" ></el-input><e…

winform的RichTextBox控件追加文本及图片(实现笔记录入和笔记搜索功能)

在工作中&#xff0c;在工作中&#xff0c;难免有一些笔记要记录下来&#xff0c;方便后续工作中快速找到。之前用的是共享文档来记录的&#xff0c;但有一个缺点就是随着写的内容越来越多&#xff0c;打开变得很慢&#xff0c;搜索更加慢&#xff0c;网络不好的时候&#xff0…