IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> socket之UDP组播(多播) -> 正文阅读

[网络协议]socket之UDP组播(多播)

1. 概述
1.1 单播用于两个主机间单对单的通信
1.2广播用于一个主机对整个局域网上所有主机上的数据通信
1.3单播和广播是两个极端,要么对一个主机进行通信,要么对整个局域网的主机进行通信
1.4实际情况下,经常需要对一组特定的主机进行通信,而不是所有局域网上的主机,这时候就有了组播
1.5IP组播(也称多址广播或多播),是一种允许一台或多台主机发送数据包到多台主机的TCP/IP网路技术。
1.6多播是 IPv6 数据包的 3 种基本目的地址类型之一,多播是一点对多点的通信, IPv6 没有采用 IPv4 中的组播术语,而是将广播看成是多播的一个特殊例子。

2.组播的特点
组播也可以称之为多播这也是 UDP 的特性之一。组播是主机间一对多的通讯模式,是一种允许一个或多个组播源发送同一报文到多个接收者的技术。组播源将一份报文发送到特定的组播地址,组播地址不同于单播地址,它并不属于特定某个主机,而是属于一组主机。一个组播地址表示一个群组,需要接收组播报文的接收者都加入这个群组。

广播只能在局域网访问内使用,组播既可以在局域网中使用,也可以用于广域网
在发送广播消息的时候,连接到局域网的客户端不管想不想都会接收到广播数据,组播可以控制发送端的消息能够被哪些接收端接收,更灵活和人性化。
广播使用的是广播地址,组播需要使用组播地址。
广播和组播属性默认都是关闭的,如果使用需要通过 setsockopt () 函数进行设置。
组播需要使用组播地址,在 IPv4 中它的范围从 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: 为本地管理组播地址,仅在特定的本地范围内有效

组播地址不属于任何服务器或个人,它有点类似一个微信群号,任何成员(组播源)往微信群(组播 IP)发送消息(组播数据),这个群里的成员(组播接收者)都会接收到此消息。

3.组播应用

3.1 点对多点应用
点对多点应用是指一个发送者,多个接收者的应用形式,这是最常见的多播应用形式。典型的应用包括:媒体广播、媒体推送、信息缓存、事件通知和状态监视等。

3.2 多点对点应用
多点对点应用是指多个发送者,一个接收者的应用形式。通常是双向请求响应应用,任何一端(多点或点)都有可能发起请求。典型应用包括:资源查找、数据收集、网络竞拍、信息询问等。

3.3 多点对多点应用
多点对多点应用是指多个发送者和多个接收者的应用形式。通常,每个接收者可以接收多个发送者发送的数据,同时,每个发送者可以把数据发送给多个接收者。典型应用包括:多点会议、资源同步、并行处理、协同处理、远程学习、讨论组、分布式交互模拟(DIS)、多人游戏等

4. 设置组播属性
如果使用组播进行数据的传输,不管是消息发送端还是接收端,都需要进行相关的属性设置,设置函数使用的是同一个,即:setsockopt(),函数原型如下:

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

4.1 发送端
发送组播消息的一端需要设置组播属性,具体的设置方式如下:

/*
struct in_addr
{
    in_addr_t s_addr;	// unsigned int
};
*/

/*
函数:setsockopt
描述:配置发送端UDP组播属性
参数:
	sockfd:	用于 UDP 通信的套接字
	level:		套接字级别,设置组播属性需要将该参数指定为:IPPTOTO_IP
	optname: 	套接字选项名,设置组播属性需要将该参数指定为:IP_MULTICAST_IF
	optval:	设置组播属性,这个指针需要指向一个 struct in_addr{} 类型的结构体地址,这个结构体地址用于存储组播地址,并且组播 IP 地址的存储方式是大端的。
	optlen:optval 指针指向的内存大小,即:sizeof(struct in_addr)
	
返回值:函数调用成功返回 0,调用失败返回 - 1	
*/

   struct in_addr opt;
   // 将组播地址初始化到这个结构体成员中即可
   inet_pton(AF_INET, GROUP_IP, &opt.s_addr);
   setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &opt, sizeof(opt));

4.2 接收端
因为一个组播地址表示一个群组,所以需要接收组播报文的接收者都加入这个群组,和想要接收群消息就必须要先入群是一个道理。加入到这个组播群组的方式如下:

/*
struct in_addr
{
    in_addr_t s_addr;	// unsigned int
};

struct ip_mreqn
{
    struct in_addr imr_multiaddr;   // 组播地址/多播地址
    struct in_addr imr_address;     // 本地地址
    int   imr_ifindex;              // 网卡的编号, 每个网卡都有一个编号
};
// 必须通过网卡名字才能得到网卡的编号: 可以通过 ifconfig 命令查看网卡名字
#include <net/if.h>
// 将网卡名转换为网卡的编号, 参数是网卡的名字, 比如: "ens33"
// 返回值就是网卡的编号
unsigned int if_nametoindex(const char *ifname);
*/

/*
函数:setsockopt
描述:配置接收端UDP组播属性
参数:
	sockfd:基于 udp 的通信的套接字
	level:套接字级别,加入到多播组该参数需要指定为:IPPTOTO_IP
	optname:套接字选项名,加入到多播组该参数需要指定为:IP_ADD_MEMBERSHIP
	optval:加入到多播组,这个指针应该指向一个 struct ip_mreqn{} 类型的结构体地址
	optlen:optval 指向的内存大小,即:sizeof(struct ip_mreqn)
*/
    struct ip_mreqn opt;
    // 要加入到哪个多播组, 通过组播地址来区分
    inet_pton(AF_INET, GROUP_IP, &opt.imr_multiaddr.s_addr);
    opt.imr_address.s_addr = htonl(INADDR_ANY);
    opt.imr_ifindex = if_nametoindex("ens33");
    setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &opt, sizeof(opt));

5. 组播通信流程
发送组播消息的一端需要将数据发送到组播地址和固定的端口上,想要接收组播消息的终端需要绑定对应的固定端口然后加入到组播的群组,最终就可以实现数据的共享。

在这里插入图片描述

6.代码举例

6.1 发送端代码

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

#define GROUP_IP "224.0.1.0"
//#define GROUP_IP "239.0.1.10"

int main()
{
    // 1. 创建通信的套接字
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd == -1)
    {
        perror("socket");
        exit(0);
    }

    // 2. 设置组播属性 (经测试可以不设置发送端组播属性也能正常发送)
    struct in_addr opt;
    // 将组播地址初始化到这个结构体成员中即可
    inet_pton(AF_INET, GROUP_IP, &opt.s_addr);
    setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &opt, sizeof(opt));

    char buf[1024];
	char sendaddrbuf[64];
	
	socklen_t len = sizeof(struct sockaddr_in);
	struct sockaddr_in sendaddr;
	
    struct sockaddr_in cliaddr;
    cliaddr.sin_family = AF_INET;
    cliaddr.sin_port = htons(9999); // 接收端需要绑定9999端口
    // 发送组播消息, 需要使用组播地址, 和设置组播属性使用的组播地址一致就可以
    inet_pton(AF_INET, GROUP_IP, &cliaddr.sin_addr.s_addr);
						
    // 3. 通信
    int num = 0;
    while(1)
    {
		memset(buf, 0, sizeof(buf));
        sprintf(buf, "hello, client...%d\n", num++);
        // 数据广播
        sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&cliaddr, len);
        printf("发送的组播的数据: %s\n", buf);
		memset(buf, 0, sizeof(buf));
		recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sendaddr, &len);
		printf("sendaddr:%s, prot:%d\n", inet_ntop(AF_INET, &sendaddr.sin_addr.s_addr,  sendaddrbuf, sizeof(sendaddrbuf)),  sendaddr.sin_port);
		printf("接收到的组播消息: %s\n", buf);
    }
    close(fd);
    return 0;
}

6.2 接收端代码

#define GROUP_IP "224.0.1.0"
//#define GROUP_IP "239.0.1.10"


int main()
{
    // 1. 创建通信的套接字
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd == -1)
    {
        perror("socket");
        exit(0);
    }

    // 2. 通信的套接字和本地的IP与端口绑定
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9999);    // 大端
    addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 0.0.0.0
    int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("bind");
        exit(0);
    }

    // 3. 加入到多播组
	#if 0 //使用struct ip_mreqn或者 struct ip_mreq 设置接收端组播属性都可以正常接收
    struct ip_mreqn opt;
    // 要加入到哪个多播组, 通过组播地址来区分
    inet_pton(AF_INET, GROUP_IP, &opt.imr_multiaddr.s_addr);
    opt.imr_address.s_addr = htonl(INADDR_ANY);
    opt.imr_ifindex = if_nametoindex("ens33");
    setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &opt, sizeof(opt));
	#else
	struct ip_mreq mreq; // 多播地址结构体
	mreq.imr_multiaddr.s_addr=inet_addr(GROUP_IP);
	mreq.imr_interface.s_addr = htonl(INADDR_ANY);	
	ret=setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
	#endif
	
    char buf[1024];
	char sendaddrbuf[64];
	socklen_t len = sizeof(struct sockaddr_in);
	struct sockaddr_in sendaddr;
	
    // 3. 通信
    while(1)
    {
        // 接收广播消息
        memset(buf, 0, sizeof(buf));
        // 阻塞等待数据达到
        recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sendaddr, &len);
		printf("sendaddr:%s, prot:%d\n", inet_ntop(AF_INET, &sendaddr.sin_addr.s_addr,  sendaddrbuf, sizeof(sendaddrbuf)),  sendaddr.sin_port);
        printf("接收到的组播消息: %s\n", buf);
		sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr *)&sendaddr, len);
    }
    close(fd);
    return 0;
}

参考于: 苏丙榅:https://subingwen.cn/linux/multicast/#4-1-%E5%8F%91%E9%80%81%E7%AB%AF

参考于:https://blog.csdn.net/weixin_43790540/article/details/104244546

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-08-14 14:29:09  更:2021-08-14 14:29:11 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 19:38:01-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码