Linux网络编程系列之UDP组播

news2024/12/30 1:58:34

一、什么是UDP组播

        UDP组播是指使用用户数据报协议(UDP)实现的组播方式。组播是一种数据传输方式,允许单一数据包同时传输到多个接收者。在UDP组播中,一个数据包可以被多个接收者同时接收,这样可以降低网络传输的负载和提高数据传输效率。

二、特性

        1、支持单向的多对多通信:UDP组播可以同时将一个数据包传输给多个接收者,使多个接收者能够同时获取到相同的数据。

        2、不可靠性:跟普通的UDP一样,UDP组播只提供不可靠的数据传输服务。如果某个接收者没有接收到数据包,发送者不会得到任何提示或反馈信息。

        3、可扩展性:UDP组播支持动态加入和退出组播组,能够自适应地处理组播成员的加入和离开(聊天群里的进群和退群操作)。

        4、低延迟:UDP组播传输的数据包不需要在接收方重新组装,可以直接进行处理,因此具有很低的传输延迟。

        5、高效:UDP组播传输的数据包只需要经过一次发送操作,就可以同时传输到多个接收者,可以有效地降低网络传输的负载。

        6、简单易用:UDP组播不需要复杂的配置和管理,使用简单,能够快速搭建起基于组播的多媒体通信系统。

三、使用场景

        1、多媒体流媒体:UDP组播可以在局域网或广域网上传输音视频流,能够快速地向多个接收者发送相同的视频和音频数据,避免了建立多个点对点的连接。

        2、 分布式应用的数据分发:UDP组播可以实现高效的数据分发,例如在大型集群环境下,可以将某些服务的状态信息广播给所有节点,使得所有节点都能够及时了解到最新的信息。

        3、网络游戏:UDP组播可以用于多人联机游戏,使得多个玩家能够同时收到相同的游戏状态和动作,提高游戏体验。

        4、 网络广播:UDP组播可以用于向多个设备广播事件和消息,例如路由器可以向所有连接的设备发送网络配置信息、DHCP服务器可以向所有设备广播IP地址信息等。

        5、实时数据更新:UDP组播可以用于实时的数据更新,例如在金融行业,可以订阅某些财经数据的实时更新,以便及时响应市场变化。

        可以把UDP组播简单理解为群聊。

四、UDP组播通信流程

        1、发送方

        (1)、建立套接字。使用socket()

        (2)、设置端口复用。使用setsockopt()(可选,推荐)

        (3)、绑定自己的IP地址和端口号。使用bind()(可以省略)

        (4)、发送数据,接收方IP地址要填写为组播地址。使用sendto()

        (5)、关闭套接字。使用close()

        2、接收方

        (1)、建立套接字。使用socket()

        (2)、定义并初始化一个组播结构体。使用struct  ip_mreq;

        (3)、给套接字加入组播属性。使用setsockopt()

        (4)、绑定自己的IP地址和端口号。使用bind(),不可以省略

        (5)、接收数据。使用recvfrom()

        (6)、关闭套接字。使用close()

五、相关函数API

          1、建立套接字

// 建立套接字 
int socket(int domain, int type, int protocol);

// 接口说明
        返回值:成功返回一个套接字文件描述符,失败返回-1

        参数domain:用来指定使用何种地址类型,有很多,具体看别的资源
            (1)PF_INET 或者 AF_INET 使用IPV4网络协议
            (2)其他很多的,看别的资源

        参数type:通信状态类型选择,有很多,具体看别的资源
            (1)SOCK_STREAM    提供双向连续且可信赖的数据流,即TCP
            (2)SOCK_DGRAM     使用不连续不可信赖的数据包连接,即UDP
    
        参数protocol:用来指定socket所使用的传输协议编号,通常不用管,一般设为0

           2、设置端口状态

// 设置端口的状态
int setsockopt(int sockfd, 
               int level, 
               int optname,
               const void *optval, 
               socklen_t optlen);


// 接口说明
        返回值:成功返回0,失败返回-1
        参数sockfd:待设置的套接字

        参数level: 待设置的网络层,一般设成为SOL_SOCKET以存取socket层

        参数optname:待设置的选项,有很多种,具体看别的资源,这里讲常用的
            (1)、SO_REUSEADDR    允许在bind()过程中本地地址可复用,即端口复用
            (2)、SO_BROADCAST    使用广播的方式发送,通常用于UDP广播
            (3)、SO_SNDBUF       设置发送的暂存区大小
            (4)、SO_RCVBUF       设置接收的暂存区大小
            (5)、IP_ADD_MEMBERSHIP 设置为组播

        参数optval:待设置的值

        参数optlen:参数optval的大小,即sizeof(optval)

// 组播结构体
struct ip_mreq
{
    struct in_addr imr_multiaddr;    // 多播组的IP地址,就是组播的IP地址
    struct in_addr imr_interface;    // 需要加入到组的IP地址,就是自己的IP地址
};    

         3、绑定IP地址和端口号

// 绑定自己的IP地址和端口号
 int bind(int sockfd, 
          const struct sockaddr *addr,
          socklen_t addrlen);

// 接口说明
        返回值:
        参数sockfd:待绑定的套接字

        参数addrlen:参数addr的大小,即sizeof(addr)

        参数addr:IP地址和端口的结构体,通用的结构体,根据sockfd的类型有不同的定义
        当sockfd的domain参数指定为IPV4时,结构体定义为
            struct sockaddr_in
            {
                unsigned short int sin_family;    // 需与sockfd的domain参数一致
                uint16_t sin_port;            // 端口号
                struct in_addr sin_addr;      // IP地址 
                unsigned char sin_zero[8];    // 保留的,未使用
            };
            struct in_addr
            {
                uin32_t s_addr;
            }
// 注意:网络通信时,采用大端字节序,所以端口号和IP地址需要调用专门的函数转换成网络字节序
    

         4、字节序转换接口 

// 第一组接口
// 主机转网络IP地址,输入主机IP地址
uint32_t htonl(uint32_t hostlong);

// 主机转网络端口,输入主机端口号
uint16_t htons(uint16_t hostshort);    // 常用

// 网络转主机IP,输入网络IP地址
uint32_t ntohl(uint32_t netlong);

// 网络转主机端口,输入网络端口
uint16_t ntohs(uint16_t netshort);


// 第二组接口,只能用于IPV4转换,IP地址
// 主机转网络
int inet_aton(const char *cp, struct in_addr *inp);

// 主机转网络
in_addr_t inet_addr(const char *cp);    // 常用

// 网络转主机
int_addr_t inet_network(const char *cp);

// 网络转主机
char *inet_ntoa(struct in_addr in);    // 常用

// 将本地IP地址转为网络IP地址
int inet_pton(int af, const char *src, void *dst);
// 参数说明:
        参数af:选择是哪一种协议族,IPV4还是IPV6
        参数src:本地IP地址
        参数dst:将本地IP地址转为网络IP地址存储到这里

           5、发送数据

// UDP协议发送数据
ssize_t sendto(int sockfd, 
               const void *buf, 
               size_t len, 
               int flags,
               const struct sockaddr *dest_addr, 
               socklen_t addrlen);

// 接口说明
        返回值:成功返回成功发送的字节数,失败返回-1
        参数sockfd:发送者的套接字
        参数buf:发送的数据缓冲区
        参数len:发送的长度
        参数flags:一般设置为0,还有其他数值,具体查询别的资源
        参数dest_addr:接收者的网络地址
        参数addrlen:接收者的网络地址大小,即sizeof(dest_addr)
    

         6、接收数据

// UDP协议接收数据
ssize_t recvfrom(int sockfd, 
                 void *buf, 
                 size_t len, 
                 int flags, 
                 struct sockaddr *src_addr, 
                 socklen_t *addrlen);

// 接口说明:
        返回值:成功返回成功接收的字节数,失败返回-1
        参数sockfd:接收者的套接字
        参数buf:接收数据缓的冲区
        参数len:接收的最大长度
        参数flags:一般设置为0,还有其他数值,具体查询别的资源
        参数src_addr:发送者的网络地址,可以设置为NULL
        参数addrlen:  发送者的网络地址大小,即sizeof(src_addr)

          7、关闭套接字

// 关闭套接字
int close(int fd);

// 接口说明
        返回值:成功返回0,失败返回-1
        参数fd:套接字文件描述符

六、案例

       实现UDP组播的演示

        发送端GroupSend.c

// UDP组播发送方的案例

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

#define SEND_IP   "192.168.64.128"    // 记得改为自己IP
#define SEND_PORT 10000   // 不能超过65535,也不要低于1000,防止端口误用

int main(int argc, char *argv[])
{
    // 1、建立套接字,使用IPV4网络地址,UDP协议
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd == -1)
    {
        perror("socket fail");
        return -1;
    }
    
    // 2、设置端口复用(推荐)
    int optval = 1; // 这里设置为端口复用,所以随便写一个值
    int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    if(ret == -1)
    {
        perror("setsockopt fail");
        close(sockfd);
        return -1;
    }

    // 3、绑定自己的IP地址和端口号(可以省略)
    struct sockaddr_in send_addr = {0};
    socklen_t addr_len = sizeof(struct sockaddr);
    send_addr.sin_family = AF_INET;   // 指定协议为IPV4地址协议
    send_addr.sin_port = htons(SEND_PORT);  // 端口号
    send_addr.sin_addr.s_addr = inet_addr(SEND_IP); // IP地址

    ret = bind(sockfd, (struct sockaddr*)&send_addr, addr_len);
    if(ret == -1)
    {
        perror("bind fail");
        close(sockfd);
        return -1;
    }

    // 4、发送数据,往组播地址
    uint16_t port = 0;  // 端口号
    char ip[20] = {0};  // IP地址
    struct sockaddr_in recv_addr = {0};

    char msg[128] = {0};    // 数据缓冲区
  
    // 注意输入组播地址,范围是D类网络地址,224.0.0.1~239.255.255.254
    printf("please input receiver IP and port\n");
    scanf("%s %hd", ip, &port);
    printf("IP = %s, port = %hd\n", ip, port);
    recv_addr.sin_family = AF_INET;   // 指定用IPV4地址
    recv_addr.sin_port = htons(port); // 接收者的端口号
    recv_addr.sin_addr.s_addr = inet_addr(ip);    // 接收者的IP地址
    
    while(getchar() != '\n');   // 清空多余的换行符
    while(1)
    {
        printf("please input data:\n");
        fgets(msg, sizeof(msg)/sizeof(msg[0]), stdin);

        // 发送数据,注意要填写接收者的地址
        ret = sendto(sockfd, msg, strlen(msg), 0, 
            (struct sockaddr*)&recv_addr, addr_len);
        if(ret > 0)
        {
            printf("success: send %d bytes\n", ret);
        }
    }
    
    // 5、关闭套接字
    close(sockfd);

    return 0;
}

        接收端GroupRecv.c

  

// UDP组播接收方的案例

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

#define RECV_IP   "192.168.64.128"    // 记得改为自己的地址

#define GROUP_IP   "224.0.0.10"       // 组播地址
#define GROUP_PORT 20000   // 不能超过65535,也不要低于1000,防止端口误用


int main(int argc, char *argv[])
{
    // 1、建立套接字,使用IPV4网络地址,UDP协议
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd == -1)
    {
        perror("socket fail");
        return -1;
    }
    
    // 2、定义并初始化一个组播结构体,设置组播IP
    struct ip_mreq vmreq;
    inet_pton(AF_INET, GROUP_IP, &vmreq.imr_multiaddr); // 初始化组播地址
    inet_pton(AF_INET, RECV_IP, &vmreq.imr_interface);  // 把自己的地址加入到组中

    // 3、给套接字加入组播属性
    int ret = setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &vmreq, sizeof(vmreq));
    if(ret == -1)
    {
        perror("setsockopt fail");
        close(sockfd);
        return -1;
    }

    // 4、绑定自己的IP地址和端口号(不可以省略)
    struct sockaddr_in recv_addr = {0};
    socklen_t addr_len = sizeof(struct sockaddr);
    recv_addr.sin_family = AF_INET;   // 指定协议为IPV4地址协议
    recv_addr.sin_port = htons(GROUP_PORT);  // 端口号,注意绑定为组播的端口号
    // recv_addr.sin_addr.s_addr = inet_addr(RECV_IP); // IP地址. 写下面的更好
    recv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 本机内所有的IP地址

    ret = bind(sockfd, (struct sockaddr*)&recv_addr, addr_len);
    if(ret == -1)
    {
        perror("bind fail");
        close(sockfd);
        return -1;
    }

    // 4、接收数据
    uint16_t port = 0;  // 端口号
    char ip[20] = {0};  // IP地址
    struct sockaddr_in send_addr = {0};
    char msg[128] = {0};    // 数据缓冲区
  
    while(1)
    {
        // 接收数据,注意使用发送者的地址来接收
        ret = recvfrom(sockfd, msg, sizeof(msg)/sizeof(msg[0]), 0, 
            (struct sockaddr*)&send_addr, &addr_len);
        if(ret > 0)
        {
            memset(ip, 0, sizeof(ip));  // 先清空IP
            strcpy(ip, inet_ntoa(send_addr.sin_addr));    // 网络IP转主机IP
            port = ntohs(send_addr.sin_port); // 网络端口号转主机端口号

            printf("[%s:%d] send data: %s\n", ip, port, msg);
            memset(msg, 0, sizeof(msg));    // 清空数据区
        }
    }
    
    // 5、关闭套接字
    close(sockfd);

    return 0;
}

      通信演示 

        注:第一幅图只有一台主机,不好演示;第二幅图有两台主机,一台本机,另外一台用ssh连接,实现了组播。

七、总结

       组播是一种数据传输方式,允许单一数据包同时传输到多个接收者。在UDP组播中,一个数据包可以被多个接收者同时接收,这样可以降低网络传输的负载和提高数据传输效率。组播主要应用以群聊的场景。UDP组播的通信流程,跟UDP的广播的通信流程大致相同,但是要注意组播接收方要定义一个组播结构体,然后把自己的IP地址加入到组播中。可以结合案例加深对组播的理解。

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

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

相关文章

P1433 吃奶酪

#include <iostream> #include <cmath> using namespace std; #define M 15 #define S(n) ((n) * (n)) double indx[M 5], indy[M 5], ans 0, sum 0;//坐标数组&#xff0c;从下标为1开始记录 int n, vis[M 5] { 0 };//vis数组&#xff0c;选过的数字标记为1…

N点复序列求2个N点实序列的快速傅里叶变换

一、方法简介 通过一个点复数序列求出两个点实数序列的离散傅里叶变换&#xff0c;进一步提升快速傅里叶变换的效率。 二、方法详解 和是实数序列&#xff0c;且长度都为&#xff0c;定义复数序列&#xff1a; &#xff0c; 则序列和可表示为&#xff1a; 的离散傅…

openssl学习——消息认证码原理

消息认证码原理 消息认证码&#xff08;Message Authentication Code, MAC&#xff09;是一种技术&#xff0c;它的原理是通过对消息和密钥进行特定的处理&#xff0c;生成一个固定长度的数据&#xff0c;这个数据就是消息认证码&#xff08;MAC&#xff09;。这个过程可以看作…

【10】基础知识:React - DOM的diffing算法

一、虚拟 DOM 中 key 的作用 react/vue 中的 key 有什么作用&#xff1f;key的内部原理是什么&#xff1f; 简单来说&#xff1a; key 是虚拟 DOM 对象的标识&#xff0c;在更新显示时 key 起着极其重要的作用&#xff0c;提高渲染效率&#xff0c;防止渲染错误。 详细的说…

和琪宝的深圳,香港之旅~

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 引言 国庆期间原定和琪宝的旅游计划是深圳-香港-厦门。但是奈何在去厦门的前一天&#xff0c;…

STM32 BootLoader设置

编写bootloader程序&#xff1a; 直接复制下面代码到自己程序中。 typedef void (*iapfun)(void); //定义一个函数类型的参数. iapfun jump2app; //设置栈顶地址 //addr:栈顶地址 __asm void MSR_MSP(u32 addr) {MSR MSP, r0 //set Main Stack valueBX r14 }//跳转到…

TikTok国际版 使用特网科技Bluestacks模拟器安装方法

特网科技Bluestacks模拟器主机 桌面自带Bluestacks模拟器 TikTok国际版Bluestacks模拟器搜索tiktot 登录google应用商店-安装TikTok 安装过程可能需要3-5分钟不等-配置过低可能会导致安装失败&#xff0c;建议升级更高内存。 安装完成-打开 安装成功APP-我的游戏查看 打开国际版…

phpcms_v9模板制作及二次开发常用代码

0:调用最新文章&#xff0c;带所在版块 {pc:get sql"SELECT a.title, a.catid, b.catid, b.catname, a.url as turl ,b.url as curl,a.id FROM v9_news a, v9_category b WHERE a.catid b.catid ORDER BY a.id DESC " num"15" cache"300"} {lo…

光电柴微电网日前调度报告

摘要 微电网是目前国内外应用较为广泛的一种绿色可再生能源&#xff0c;近几年我国微电网产业的发展十分迅速。然后&#xff0c;越来越多的微电网系统建立并网&#xff0c;微电网产生的电能受外界因素影响较大&#xff0c;具有一定的随机性和波动性&#xff0c;给并网后的电力系…

leetCode 72. 编辑距离 动态规划

72. 编辑距离 - 力扣&#xff08;LeetCode&#xff09; 给你两个单词 word1 和 word2&#xff0c; 请返回将 word1 转换成 word2 所使用的最少操作数 。你可以对一个单词进行如下三种操作&#xff1a; 插入一个字符删除一个字符替换一个字符 编辑距离的应用场景&#xff1a;…

【内网攻击】DHCP协议概念——地址池耗尽攻击

目录 前言 DHCP 服务概念 1&#xff09;客户端发送DHCP Discovery广播包 2&#xff09;服务器响应DHCP Offer广播包 3&#xff09;客户机发送DHCP Request广播包 4&#xff09;服务器发送DHCP ACK广播包 部署DHCP服务器 dhcp地址池消耗攻击 攻击防御 前言 现在思考我们…

[论文分享] EnBinDiff: Identifying Data-Only Patches for Binaries

EnBinDiff: Identifying Data-Only Patches for Binaries [TDSC 2021] 在本文中&#xff0c;我们将重点介绍纯数据补丁&#xff0c;这是一种不会引起任何结构更改的特定类型的安全补丁。作为导致假阴性的最重要原因之一&#xff0c;纯数据补丁成为影响所有最先进的二进制差分方…

spring security 认证授权详解

spring security简介 Spring Security 是 Spring家族中的一个安全管理框架&#xff0c;它提供了更丰富的功能做认证授权 认证&#xff1a;当前用户有没有权限登录&#xff0c;是否为本系统用户授权&#xff1a;当前登录的用户有没有操作功能的权限 spring security的搭建 引入…

SqlServer安装教程

百度网盘地址: 链接&#xff1a;https://pan.baidu.com/s/1ntqoK9uVc6fBVTm7twh8kw 提取码&#xff1a;grdt 安装: 双击:SQLEXPRADV_x64_CHS.exe ,等待;点击计划,系统配置检查器,根据要求修改(我被要求重启了)点击安装,全新SQL Server独立安装或向现有安装添加功能,接受功能选…

颜色特征和sift算法结合的指示类交通标志识别

目录 摘 要...................................................................................... 3 第一章 绪论........................................................................ 6 1.1 研究课题背景...................................................…

fatal error C1083: 无法打开包括文件: “ta_libc.h”: No such file or directory

用python做交易数据分析时&#xff0c;可以用talib库计算各类指标&#xff0c;这个库通过以下命令安装&#xff1a; pip install TA-Lib -i https://pypi.tuna.tsinghua.edu.cn/simple windows安装时可能出现本文标题所示的错误&#xff0c;可按如下步骤解决&#xff1a; 1、去…

【Docker 内核详解】namespace 资源隔离(四):Mount namespace Network namespace

namespace 资源隔离&#xff08;四&#xff09;&#xff1a;Mount namespace & Network namespace 1.Mount namespace mount namespace 通过隔离文件系统挂载点对隔离文件系统提供支持&#xff0c;它是历史上第一个 Linux namespace&#xff0c;所以标识位比较特殊&#x…

云开发校园宿舍/企业/部门/物业故障报修小程序源码

微信小程序云开发校园宿舍企业单位部门物业报修小程序源码&#xff0c;这是一款云开发校园宿舍报修助手工具系统微信小程序源码&#xff0c;适用于学校机房、公司设备、物业管理以及其他团队后勤部&#xff0c;系统为简单云开发&#xff0c;不需要服务器域名即可部署&#xff0…

[HNCTF 2022 WEEK2]ez_ssrf题目解析

这题主要是引入ssrf这个漏洞攻击&#xff0c;本质上没有更深入的考察 本题是需要我们去伪造一个ssrf的请求头去绕过 题目开始给了我们信息让我们去访问index.php fsockopen函数触发ssrf fsockopen() 函数建立与指定主机和端口的 socket 连接。然后&#xff0c;它将传入的 bas…

nginx的location的优先级和匹配方式

nginx的location的优先级和匹配方式 在http模块中有server&#xff0c;server模块中有location&#xff0c;location匹配的是uri 在一个server中&#xff0c;会有多个location&#xff0c;如何来确定匹配哪个location niginx的正则表达式 ^ 字符串的起始位置 $ 字符串的…