【Linux C | 网络编程】多播 | 套接字选项详解及C语言例子

本文详细介绍了多播套接字选项在Linux环境下的使用,包括IPv4和IPv6的加入、离开多播组,阻塞/开通特定源,以及控制数据报的外出接口、TTL和回馈等,通过C语言代码示例进行讲解。
摘要由CSDN通过智能技术生成

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍 🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭
⏰发布时间⏰:

本文未经允许,不得转发!!!


在这里插入图片描述

🎄一、概述

前面文章:多播的概念、多播地址、UDP实现多播的C语言例子,介绍了多播的相关概念、知识点和多播的C语言例子。但是对多播相关的套接字选项介绍得比较少,本文要对多播套接字选项进行详细的说明,学习完将对多播更多地了解。

多播套接字选项主要有以下九个类型,包含了IPv4、IPv6、IP版本无关的类型,如下表:

类型说明选项名(IPv4、IPv6、IP版本无关)数据类型(IPv4、IPv6、IP版本无关)
加入一个多播组IP_ADD_MEMBERSHIP / IPV6_JOIN_GROJP / MCAST_JOIN_GROUPstruct ip_mreq / struct ipv6_mreq / struct group_req
离开一个多播组IP_DROP_MEMBERSHIP / IPV6_LEAVE_GROUP / MCAST_LEAVE_GROUPstruct ip_mreq / struct ipv6_mreq / struct group_req
在已加入组上阻塞某个源IP_BLOCK_SOURCE / MCAST_BLOCK_SOURCEstruct ip_mreq_source / struct group_source_req
开通一个早先阻塞的源IP_UNBLOCK_SOURCE / MCAST_UNBLOCK_SOURCEstruct ip_mreq_source / struct group_source_req
加入一个源特定多播组IP_ADD_SOURCE_MEMBERSHIP / MCAST_JOIN_SOURCE_GROUPstruct ip_mreq_source / struct group_source_req
离开一个源特定多播组IP_DROP_SOURCE_MEMBERSHIP / MCAST_LEAVE_SOURCE_GROUPstruct ip_mreq_source / struct group_source_req
指定外出多播数据报的默认接IP_MULTICAST_IF / IPV6_MULTICAST_IFstruct in_addr / u_int
指定外出多播数据报的TTL/跳限IP_MULTICAST_TTL / IPV6_MULTICAST_HOPSu_char / int
开启或禁止外出多播数据报的回馈IP_MULTICAST_LOOP / IPV6_MULTICAST_LOOPu_char / u_int

表格中,前面6个选项是在多播接收端操作的,后面三个选项是多播发送端操作的。


在这里插入图片描述

🎄二、加入、离开一个多播组

加入或离开一个多播组 是多播接收端的操作,一个UDP套接字必须加入多播组之后才可以接收到多播数据报。
下面三个结构体是加入或离开一个多播组会使用的结构体,定义在netinet/in.h头文件中:

#include <netinet/in.h>
struct ip_mreq
{
	struct in_addr imr_multiaddr;	/* IP multicast address of group.  */
	struct in_addr imr_interface;	/* Local IP address of interface.  */
};

struct ipv6_mreq
{
	struct in6_addr ipv6mr_multiaddr;/* IPv6 multicast address of group */
	unsigned int ipv6mr_interface;	/* local interface */
};

struct group_req
{
	uint32_t gr_interface;			/* Interface index.  */
	struct sockaddr_storage gr_group;/* Group address.  */
};

✨2.1 加入一个多播组(IP_ADD_MEMBERSHIP、IPV6_JOIN_GROJP、MCAST_JOIN_GROUP)

加入多播组需要指定一个多播组地址和一个本地接口,通过setsockopt函数结合上面的几个结构体进行操作,几种类型操作要点如下:

  • IPv4 :使用 IP_ADD_MEMBERSHIP,struct ip_mreq 进行操作;
    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr("239.0.1.1");	// 多播组的IP地址
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);		// 加入的客服端主机IP地址
    if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)    {
        perror("setsockopt");
        return -1;
    }
    
  • IPv6 :使用 IPV6_JOIN_GROJP,struct ipv6_mreq 进行操作;
  • 协议无关:使用 MCAST_JOIN_GROUP,struct group_req 进行操作。

注意
1、如果指定本地接口为 INADDR_ANY,表示由内核选择一个本地接口。
2、一个本地接口可以加入多个多播组。
3、多个本地接口可以加入同一个多播组。


✨2.2 离开一个多播组(IP_DROP_MEMBERSHIP、IPV6_LEAVE_GROUP、MCAST_LEAVE_GROUP)

IP_DROP_MEMBERSHIP、IPV6_LEAVE_GROUP、MCAST_LEAVE_GROUP 这几个套接字选项都是让指定的本地接口离开指定多播组的。分类型使用如下:

  • IPv4 :使用 IP_DROP_MEMBERSHIP,struct ip_mreq 进行操作;
    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr("239.0.1.1");	// 多播组的IP地址
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);		// 加入的客服端主机IP地址
    setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP,&mreq, sizeof(mreq)); 
    
  • IPv6 :使用 IPV6_LEAVE_GROUP,struct ipv6_mreq 进行操作;
  • 协议无关:使用 MCAST_LEAVE_GROUP,struct group_req 进行操作。

注意
1、如果未指定本地接口(也就是值为 INADDR_ANY),那么抹除首个匹配到的多播组成员关系。
2、如果一个套接字加入多播组后从不显式离开该组,那么该套接字关闭时,成员关系会自动抹除。
3、如果多个本地接口加入了同一个多播组,则单个套接字上成员关系的抹除不影响该主机继续作为该多播组的成员。


🌰2.3 举例子

下面是加入、离开一个多播组的接收端代码:

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

int main()
{
	// 1、创建UDP套接字socket
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd<0)
		perror("socket error" );
	
	// 2、准备本地ip接口和多播组端口
	struct sockaddr_in servaddr;
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons (10086);
	servaddr.sin_addr.s_addr = INADDR_ANY; // 指定ip地址为 INADDR_ANY,这样要是服务器主机有多个网络接口,服务器进程就可以在任一网络接口上接受客户端的连接
	
	// 3、绑定多播组端口 bind
	if (bind(sockfd,(struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
		perror("bind error" );
	
	// 4、加入多播组 239.0.1.1
	struct ip_mreq mreq;
	mreq.imr_multiaddr.s_addr = inet_addr("239.0.1.1");	// 多播组的IP地址
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);
    if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)    {
        perror("setsockopt");
        return -1;
    }
	
	// 5、使用 sendto、recvfrom 交互数据
	printf("UdpSer sockfd=%d, start \n",sockfd);
	char recvline[256];
	while(1)
	{
		struct sockaddr_in cliaddr;
		bzero(&cliaddr, sizeof(cliaddr));
		socklen_t addrLen=sizeof(cliaddr);
		int n = recvfrom(sockfd, recvline, sizeof(recvline), 0, (struct sockaddr*)&cliaddr, &addrLen);
		if(n>0)
		{
			recvline[n] = 0 ;/*null terminate */
			printf("recv sockfd=%d %d byte, [%s] addrLen=%d, cliIp=%s, cliPort=%d\n",
				sockfd, n, recvline, addrLen, inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
			sendto(sockfd, "Hello,I am udp server", strlen("Hello,I am udp server"), 0, (struct sockaddr*)&cliaddr, addrLen);
		}
	}
	
	// 6、离开多播组
	setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP,&mreq, sizeof(mreq)); 
	
	// 7、关闭
	close(sockfd);

	return 0;
}


在这里插入图片描述

🎄三、阻塞、开通接收某个源的多播分组

针对上个小节介绍的不限定源的多播组,系统提供了阻塞接收某个源的多播分组的功能。使用IP_BLOCK_SOURCE、MCAST_BLOCK_SOURCE这两个套接字选项可以阻塞某个源的多播分组,使用IP_UNBLOCK_SOURCE、MCAST_UNBLOCK_SOURCE可以开通之前阻塞的源。

下面两个结构体是阻塞、开通接收某个源的多播分组以及下一小节加入或离开一个源特定多播组会使用的结构体,定义在netinet/in.h头文件中:

struct ip_mreq_source
{
	struct in_addr imr_multiaddr;	/* IP multicast address of group.  */
	struct in_addr imr_sourceaddr;	/* IP address of source.  */
	struct in_addr imr_interface;	/* IP address of interface.  */
};

struct group_source_req
{
	uint32_t 				gsr_interface;/* Interface index.  */
	struct sockaddr_storage gsr_group;	/* Group address.  */
	struct sockaddr_storage gsr_source;	/* Source address.  */
};

✨3.1 阻塞接收某个源的多播分组(IP_BLOCK_SOURCE、MCAST_BLOCK_SOURCE)

Linux阻塞接收某个源的多播分组功能支持IPv4协议无关两个选项,结合上面的结构体,使用如下:

  • IPv4 :使用 IP_BLOCK_SOURCE,struct ip_mreq_source 进行操作。下面是示例代码,阻塞后,192.168.2.183主机发到该多播组的数据报不会被接收:
    struct ip_mreq_source block_mreq;
    block_mreq.imr_multiaddr.s_addr = inet_addr("239.0.1.1");	// 多播组的IP地址
    block_mreq.imr_sourceaddr.s_addr = inet_addr("192.168.2.183");	// 源地址
    block_mreq.imr_interface.s_addr = htonl(INADDR_ANY);		
    if (setsockopt(sockfd, IPPROTO_IP, IP_BLOCK_SOURCE, &block_mreq, sizeof(block_mreq)) == -1)    {
        perror("setsockopt");
        return -1;
    }
    
  • 协议无关:使用 MCAST_BLOCK_SOURCE,struct group_source_req 进行操作。

✨3.2 开通接收某个源的多播分组(IP_UNBLOCK_SOURCE、MCAST_UNBLOCK_SOURCE)

IP_UNBLOCK_SOURCE、MCAST_UNBLOCK_SOURCE 这2个套接字选项用于开通之前阻塞的源。也是支持IPv4协议无关两个选项,结合上面的结构体,使用如下:

  • IPv4 :使用 IP_UNBLOCK_SOURCE,struct ip_mreq_source 进行操作;
    struct ip_mreq_source block_mreq;
    block_mreq.imr_multiaddr.s_addr = inet_addr("239.0.1.1");	// 多播组的IP地址
    block_mreq.imr_sourceaddr.s_addr = inet_addr("192.168.2.183");	// 源地址
    block_mreq.imr_interface.s_addr = htonl(INADDR_ANY);	
    setsockopt(sockfd, IPPROTO_IP, IP_UNBLOCK_SOURCE,&mreq, sizeof(mreq)); 
    
  • 协议无关:使用 MCAST_UNBLOCK_SOURCE,struct group_source_req 进行操作。

注意
1、如果未指定本地接口(也就是值为 INADDR_ANY),那么开通首个匹配的被阻塞源。


在这里插入图片描述

🎄四、加入、离开一个源特定多播组

加入或离开一个源特定多播组 也是多播接收端的操作,第二小节介绍的是不限定源的,接收端加入多播组后,任何IP向该多播组发送的数据报都会被接收;如果加入的是源特定多播组,就只有加入多播组时指定的源IP发送到多播组的数据报才被接收。

✨4.1 加入一个源特定多播组(IP_ADD_SOURCE_MEMBERSHIP、MCAST_JOIN_SOURCE_GROUP)

加入多播组需要指定一个多播组地址和一个本地接口,通过setsockopt函数结合上面的结构体进行操作,2种类型操作要点如下:

  • IPv4 :使用 IP_ADD_SOURCE_MEMBERSHIP,struct ip_mreq_source 进行操作。下面是示例代码,加入多播组后,只有192.168.2.183的主机往该多播组发送的数据报才被接收;
    struct ip_mreq_source mreq;
    mreq.imr_multiaddr.s_addr = inet_addr("239.0.1.1");	// 多播组的IP地址
    mreq.imr_sourceaddr.s_addr = inet_addr("192.168.2.183");	// 源地址
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);		
    if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)    {
        perror("setsockopt");
        return -1;
    }
    
  • 协议无关:使用 MCAST_JOIN_SOURCE_GROUP,struct group_source_req 进行操作。

注意
1、如果指定本地接口为 INADDR_ANY,表示由内核选择与首个匹配的多播组成员关系对应的本地接口。
2、修改的多播组必须是之前已加入的非限定源多播组。


✨4.2 离开一个源特定多播组(IP_DROP_SOURCE_MEMBERSHIP、MCAST_LEAVE_SOURCE_GROUP)

IP_DROP_SOURCE_MEMBERSHIP、MCAST_LEAVE_SOURCE_GROUP 2个套接字选项都是让指定的本地接口离开指定指定源的多播组的。分类型使用如下:

  • IPv4 :使用 IP_DROP_SOURCE_MEMBERSHIP,struct ip_mreq_source 进行操作;
    struct ip_mreq_source mreq;
    mreq.imr_multiaddr.s_addr = inet_addr("239.0.1.1");	// 多播组的IP地址
    mreq.imr_sourceaddr.s_addr = inet_addr("192.168.2.183");	// 源地址
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);		// 加入的客服端主机IP地址
    setsockopt(sockfd, IPPROTO_IP, IP_DROP_SOURCE_MEMBERSHIP,&mreq, sizeof(mreq)); 
    
  • 协议无关:使用 MCAST_LEAVE_SOURCE_GROUP,struct group_source_req 进行操作。

注意
1、如果未指定本地接口(也就是值为 INADDR_ANY),那么抹除首个匹配到的特定源多播组成员关系。
2、如果一个套接字加入特定源多播组后从不显式离开该组,那么该套接字关闭时,成员关系会自动抹除。
3、如果多个本地接口加入了同一个特定源多播组,则单个套接字上成员关系的抹除不影响该主机继续作为该特定源多播组的成员。


🌰4.3 举例子

下面是加入、离开一个多播组的接收端代码:

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

int main()
{
	// 1、创建UDP套接字socket
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd<0)
		perror("socket error" );
	
	// 2、准备本地ip接口和多播组端口
	struct sockaddr_in servaddr;
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons (10086);
	servaddr.sin_addr.s_addr = INADDR_ANY; // 指定ip地址为 INADDR_ANY,这样要是服务器主机有多个网络接口,服务器进程就可以在任一网络接口上接受客户端的连接
	
	// 3、绑定多播组端口 bind
	if (bind(sockfd,(struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
		perror("bind error" );
	
	// 4、加入多播组 239.0.1.1
	struct ip_mreq_source mreq;
	mreq.imr_multiaddr.s_addr = inet_addr("239.0.1.1");	// 多播组的IP地址
	mreq.imr_sourceaddr.s_addr = inet_addr("192.168.2.183");	// 源地址
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);		
    if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)    {
        perror("setsockopt");
        return -1;
    }
	
	// 5、使用 sendto、recvfrom 交互数据
	printf("UdpSer sockfd=%d, start \n",sockfd);
	char recvline[256];
	while(1)
	{
		struct sockaddr_in cliaddr;
		bzero(&cliaddr, sizeof(cliaddr));
		socklen_t addrLen=sizeof(cliaddr);
		int n = recvfrom(sockfd, recvline, sizeof(recvline), 0, (struct sockaddr*)&cliaddr, &addrLen);
		if(n>0)
		{
			recvline[n] = 0 ;/*null terminate */
			printf("recv sockfd=%d %d byte, [%s] addrLen=%d, cliIp=%s, cliPort=%d\n",
				sockfd, n, recvline, addrLen, inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
			sendto(sockfd, "Hello,I am udp server", strlen("Hello,I am udp server"), 0, (struct sockaddr*)&cliaddr, addrLen);
		}
	}
	
	// 6、离开多播组
	setsockopt(sockfd, IPPROTO_IP, IP_DROP_SOURCE_MEMBERSHIP,&mreq, sizeof(mreq));
	
	// 7、关闭
	close(sockfd);

	return 0;
}


在这里插入图片描述

🎄五、多播数据报的外出接口、TTL或跳限、本地回环

前面介绍的6个套接字选项都是多播组接收端的,这小节介绍发送端的3个套接字选项。首先,明确一点,多播数据报发送端是可以什么都不设置的,下面的三个选项都是可选的。

IP_MULTICAST_IF、IPV6_MULTICAST_IF
IP_MULTICAST_IF、IPV6_MULTICAST_IF 分别设置IPv4、IPv6的发送多播数据报的外出接口,对于IPv4,使用struct in_addr结构体指定,对于IPv6,该接口由接口索引指定。如果设置的值为IPv4的INADDR_ANY或IPv6的索引0,那么先前通过本选项设置的任何接口都会被抹除。

IP_MULTICAST_TTL、IPV6_MULTICAST_HOPS
给外出的多播数据报设置IPv4的TTL或IPv6的跳限。如果不指定,这两个版本就都默认为1,从而把多播数据报限制在本地子网。

IP_MULTICAST_LOOP / IPV6_MULTICAST_LOOP
开启或禁止多播数据报的本地自环(即回馈)。默认情况下回馈开启:如果一个主机在某个外出接口上属于某个多播组,那么该主机上由某个进程发送的目的地为该多播组的每个数据报都有一个副本回馈,被该主机作为一个收取的数据报处理。

在这里插入图片描述

🎄六、总结

👉本文介绍了网络编程中多播相关的9个套接字选项,并给出了一些选项使用的C语言例子

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

  • 18
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wkd_007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值