Linux网络编程系列之UDP组播

Linux网络编程系列  (够吃,管饱)

        1、Linux网络编程系列之网络编程基础

        2、Linux网络编程系列之TCP协议编程

        3、Linux网络编程系列之UDP协议编程

        4、Linux网络编程系列之UDP广播

        5、Linux网络编程系列之UDP组播

        6、Linux网络编程系列之服务器编程——阻塞IO模型

        7、Linux网络编程系列之服务器编程——非阻塞IO模型

        8、Linux网络编程系列之服务器编程——多路复用模型

        9、Linux网络编程系列之服务器编程——信号驱动模型

一、什么是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.254printf("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、定义并初始化一个组播结构体,设置组播IPstruct 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));  // 先清空IPstrcpy(ip, inet_ntoa(send_addr.sin_addr));    // 网络IP转主机IPport = 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.mzph.cn/news/107286.shtml

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

相关文章

尚硅谷Flink(一)

目录 ☄️前置工作 fenfa脚本 &#x1f30b;概述 ☄️Flink是什么 ☄️特点&#xff08;多nb&#xff09; ☄️应用场景&#xff08;不用看&#xff09; ☄️分层API &#x1f30b;配环境 ☄️wordcount ☄️WcDemoUnboundStreaming &#x1f30b;集群部署 ☄️集…

xml的语法

<!-- 1、每一个xml,有且只有一个根标签&#xff0c;所有xml标签必须写在根标签中 2、标签必须要有合闭 3、xml格式是否正确&#xff0c;可以通过浏览器打开xml。来校验xml格式是否正确 4、xml是区别大小写的 5、xml书写标签名时&#xff0c;不要出现空格等特…

微信小程序的框架

目录 一、视图层 1. WXML 数据绑定 列表渲染 条件渲染 模板 2. WXSS 尺寸单位 样式导入 内联样式 选择器 3. WXS事件 二、逻辑层 1. 页面生命周期 2.跳转 1. 一级跳一级 2. 一级跳二级 3. 二级跳二级 4. 二级跳一级 总结 带给我们的收获 一、视图层 1. …

idea 启动项目报错 Command line is too long

1.idea 启动报错 Command line is too long&#xff0c;启动报错信息&#xff1a;Error running ‘Application‘: Command line is too long. 2.如何解决&#xff1f; 1&#xff09;idea打开一个项目。 2.打开项目下的*.idea* 文件夹下的 workspace.xml 文件。 3.在<co…

springboot 导出word模板

一、安装依赖 <dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.12.1</version></dependency>二、定义工具类 package com.example.springbootmp.utils;import com.deepoove.poi.XWP…

如何搬运视频赚钱?

可以写一个爬虫&#xff0c;爬取热门视频并下载&#xff0c;然后自动发布到对应网站。 具体实现步骤。 使用的技术栈是Nodejs&#xff0c;安装 Puppeteer npm install puppeteer创建一个 Puppeteer 脚本来访问抖音网站并爬取视频内容。 const puppeteer require(puppeteer…

5.Python-使用XMLHttpRequest对象来发送Ajax请求

题记 使用XMLHttpRequest对象来发送Ajax请求&#xff0c;以下是一个简单的实例和操作过程。 安装flask模块 pip install flask 安装mysql.connector模块 pip install mysql-connector-python 编写app.py文件 app.py文件如下&#xff1a; from flask import Flask, reque…

spring boot 下载resources下的静态文件为流格式

废话不多说&#xff0c;直接上代码 一、下载逻辑 public void downAppApk(HttpServletResponse response){ClassPathResource classPathResource new ClassPathResource("app/xxxxxx.apk");if (!classPathResource.exists()) {throw new BusinessException("安…

好用的跨平台同步笔记工具,手机和电脑可同步的笔记工具

在这个快节奏的工作环境中&#xff0c;每个人都在寻找一种方便又高效的方式来记录工作笔记。记录工作笔记可以帮助大家统计工作进展&#xff0c;了解工作进程&#xff0c;而如果工作中常在一个地方办公&#xff0c;直接选择电脑或者手机中笔记工具来记录即可&#xff0c;但是对…

[部署网站]01安装宝塔面板搭建WordPress

宝塔面板安装WordPress&#xff08;超详细&#xff09;_Wordpress主题网 参考教程 宝塔面板 - 简单好用的Linux/Windows服务器运维管理面板 官网 1.首先你需要一个服务器或者主机 &#xff08;Windows系统或者Linux系统都可以&#xff09; 推荐Linux系统更稳定&#xff0c;…

centos7安装docker-compose

1、下载docker-compose 网盘下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1pltG3aWiN9zMqYt7ZVyLng 提取码&#xff1a;7awd --来自百度网盘超级会员V5的分享2、上传docker-compose文件到/usr/local/bin目录 [root192 bin]# ll 总用量 53892 -rwxrwxrwx 1 …

service worker实现静态资源缓存

service worker如何实现静态资源缓存和强制更新&#xff0c;请看如下示例&#xff1a; 1、注册service worker function serviceWorker() {if (isLocalStorageAvailable() && serviceWorker in navigator) {navigator.serviceWorker.register(/static/almasaeed2010/…

Axure RP医疗在线挂号问诊原型图医院APP原形模板

医疗在线挂号问诊Axure RP原型图医院APP原形模板&#xff0c;是一款原创的医疗类APP&#xff0c;设计尺寸采用iPhone13&#xff08;375*812px&#xff09;&#xff0c;原型图上加入了仿真手机壳&#xff0c;使得预览效果更加逼真。 本套原型图主要功能有医疗常识科普、医院挂号…

海思平台SS528V100编译 linux kernel tun.c eth_get_headlen 编译 出错的问题

osdrv目录下 make kernel 会去opensource目录下解压linux内核压缩包 同时打上很多补丁 如上图 查看Makefile 如下 有打补丁的命令 然后 然后我们的应用程序用到一个特性 需要打开tun/tab这两个属性 打开之后编译内核出错 查到最后发现 没打补丁之前的文件 没问题 …

Qt拖拽文件到窗口、快捷方式打开

大部分客户端都支持拖拽文件的功能&#xff0c;本篇博客介绍Qt如何实现文件拖拽到窗口、快捷方式打开&#xff0c;以我的开源视频播放器项目为例&#xff0c;介绍拖拽视频到播放器窗口打开。   需要注意的是&#xff0c;Qt拖拽文件的功能&#xff0c;不支持以管理员权限启动的…

maven 新建模块 导入后 按Ctrl 点不进新建模块pom定义

新建的ruoyi-common-mybatisplus 模块,导入一直不正常 画出的模块一直导入不进来 这是提示信息 这是正常的提示信息 加上 <version>3.6.3</version> 后,才一切正常

攻防世界题目练习——Web引导模式(三)(持续更新)

题目目录 1. mfw2.3.4.5. 1. mfw 进去看到网页和页面内容如下&#xff1a; 看到url的参数 ?pageabout &#xff0c;我以为是文件包含什么的&#xff0c;反复试了几次&#xff0c;想用 …/…/…/…/etc/passwd &#xff0c;但是发现.似乎被过滤了&#xff0c;实在不知道怎么做…

uniapp(uncloud) 使用生态开发接口详情2(使用 schema创建数据, schema2code创建页面, iconfont 引入项目)

上一篇介绍如何创建项目,接下来该是如何使用 在项目中pages 目录下,新建界面 项目运行,浏览器中用账号密码登录, 新建一级和二级页面 2.1 系统管理 > 菜单管理 (新增一级界面) 2.2 找到刚刚创建的菜单, 操作行有 子菜单(点击) 用DB Schema创建页面, 3.1 在uniCloud > d…

开路、断路和短路区别

文章目录 开路和断路击穿电源短路、用电器短路、对地短路和对电源短路 开路和断路 开路和断路是电路中两种用于描述电流流动情况的状态。 两者易混淆&#xff0c;常被混淆使用&#xff0c;但是它们还是有所不同。 开路表示电路中存在一个断链&#xff0c;电流无法从一个点流到…

idea自定义 postfix completion提高编码效率

postfix completion的使用 详情见&#xff1a; https://www.cnblogs.com/expiator/p/17380495.html 自定义 postfix completion List、 String 初始化list&#xff1a; key: list表达式&#xff1a; List<$EXPR$> $END$List new ArrayList<>();字符串判空&…