• 网络编程之多播


    简介

    单播数据只能发送给单个目的主机,一个发送方、一个接收方,两个主机间的通信不影响其他主机。广播数据是通过广播地址将数据发送给局域网上的所有主机,广播一般只在单个局域网中传播。
    多播和广播一样也是将数据发送给多个主机,但仅将数据发送给属于相同多播组的主机,而且多播数据能通过路由器进行传播。

    IGMP

    多播使用的协议是IGMP(Internet Group Management Protocol),该协议是IP协议簇的一个组成部分,可在IP首部的协议字段设置为2,标记为IGMP协议。IGMP共有三个版本:IGMPv1、IGMPv2、IGMPv3。

    版本加入多播组离开多播组多播源过滤
    IGMPv1××
    IGMPv2×
    IGMPv3
    多播源过滤是指可设置接收多播数据的主机白名单。这个功能是IGMPv3才有的,Windows Vista及以上版本才支持IGMPv3。

    多播地址

    多播地址属于D类IP地址,前4位为固定的1110,剩余的28位用于标识多播组。

    前4位后28位
    1110多播组标识
    用十进制表示多播地址范围是: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为本地管理组播地址,仅在特定的本地范围内有效。
    固定的多播组地址是是永久的,它们的组员个数在任意时刻是变化的,甚至是0个;其他临时多播组则只有组员个数大于0时才存在。

    RFC官方规定了一些固定的多播组地址:

    多播地址用途
    224.0.0.0保留地址
    224.0.0.1子网所有组播主机,含网关
    224.0.0.2子网所有组播路由器
    224.0.0.4DRMRP 路由器
    224.0.0.5所有OSPF的路由器
    224.0.0.6OSPF指定路由器
    224.0.0.10EIGRP 路由器
    224.0.0.12DHCP服务器/中继器
    224.0.1.1NTP 网络时间协议

    组播相关结构体

    https://docs.microsoft.com/en-us/windows/win32/api/ws2ipdef/ns-ws2ipdef-ip_mreq

    typedef struct ip_mreq {
      IN_ADDR imr_multiaddr;
      IN_ADDR imr_interface;
    } IP_MREQ, *PIP_MREQ;
    
    • 1
    • 2
    • 3
    • 4

    成员①:多播组IP
    成员②:本地IP地址

    typedef struct ip_mreq_source {
      IN_ADDR imr_multiaddr;
      IN_ADDR imr_sourceaddr;
      IN_ADDR imr_interface;
    } IP_MREQ_SOURCE, *PIP_MREQ_SOURCE;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    成员①:多播组IP
    成员②:源地址黑/白名单
    成员③:本地IP地址

    多播实例

    这里只有一台机器,但是仍然能够看出来和简单的单播不一样的地方,发送的目标是组播IP,接收的时候使用本地IP

    发送

    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #include
    #include
    #include
    
    #pragma comment(lib, "ws2_32.lib")
    
    //项目->属性->高级,字符集选择【使用多字符集】
    int main()
    {
    	WORD wVersionRequested = MAKEWORD(2, 2);//版本
    	WSADATA wsaDATA;
    
    	//打开网络库
    	if (WSAStartup(wVersionRequested, &wsaDATA) != 0)
    	{
    		printf("打开网络库失败!\n");
    		return -1;
    	}	
    	
    	ip_mreq mreq;	
    	int TTL = 8;
    
    	mreq.imr_interface.S_un.S_addr = inet_addr("127.0.0.1");//本地IP
    	mreq.imr_multiaddr.S_un.S_addr = inet_addr("234.2.3.4");//多播组IP
    	
    	SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);//创建Socket句柄,多播只支持UDP
    
    	if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&TTL, sizeof(TTL)) != 0)//设置TTL
    	{
    		printf("setsockopt设置TTL失败!\n");
    		closesocket(sock);//关闭Socket句柄
    		WSACleanup();//关闭网络库
    		return -1;
    	}
    
    	if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(ip_mreq)) != 0)//加入多播组
    	{
    		printf("setsockopt设置TTL失败!\n");
    		closesocket(sock);//关闭Socket句柄
    		WSACleanup();//关闭网络库
    		return -1;
    	}
    
    	struct sockaddr_in multiif;
    	multiif.sin_family = AF_INET;
    	multiif.sin_port = htons(9527);//用htons宏将整型转为端口号的无符号整型
    
    	multiif.sin_addr.S_un.S_addr = inet_addr("234.2.3.4");//使用多播IP作为目标IP  
    	//multiif.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//相当于绑定到0.0.0.0
    
    	int i = 0;
    	char Sendbuf[0x100] = { 0 };
    
    	while (1)
    	{
    		sprintf_s(Sendbuf,"%d", i);//将自增量写入发送变量
    		i++;
    		//也可以用connect+send组合
    		if (sendto(sock, Sendbuf, sizeof(Sendbuf), 0, (const struct sockaddr*)&multiif, sizeof(sockaddr_in)) == SOCKET_ERROR)
    		{
    			int err = WSAGetLastError();//取错误码
    			printf("服务器sendto失败错误码为:%d\n", err);
    			continue;
    		}
    		Sleep(1000);//休息1秒钟再发送
    	}
    	
    
    	closesocket(sock);//关闭Socket句柄
    	WSACleanup();//关闭网络库
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    接收

    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #include
    #include
    #include
    
    #pragma comment(lib, "ws2_32.lib")
    
    int main()
    {
    	WORD wVersionRequested = MAKEWORD(2, 2);//版本
    	WSADATA wsaDATA;
    
    	//打开网络库
    	if (WSAStartup(wVersionRequested, &wsaDATA) != 0)
    	{
    		printf("打开网络库失败!\n");
    		return -1;
    	}
    
    	ip_mreq mreq;
    	int TTL = 8;
    
    	mreq.imr_interface.S_un.S_addr = inet_addr("127.0.0.1");//本地IP
    	mreq.imr_multiaddr.S_un.S_addr = inet_addr("234.2.3.4");//多播组IP
    
    	SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);//创建Socket句柄,多播只支持UDP
    
    	if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&TTL, sizeof(TTL)) != 0)//设置TTL
    	{
    		printf("setsockopt设置TTL失败!\n");
    		closesocket(sock);//关闭Socket句柄
    		WSACleanup();//关闭网络库
    		return -1;
    	}
    
    	if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(ip_mreq)) != 0)//加入多播组
    	{
    		printf("setsockopt设置TTL失败!\n");
    		closesocket(sock);//关闭Socket句柄
    		WSACleanup();//关闭网络库
    		return -1;
    	}
    
    	struct sockaddr_in localif;
    	localif.sin_family = AF_INET;
    	localif.sin_port = htons(9527);//用htons宏将整型转为端口号的无符号整型
    
    	localif.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//使用多播IP作为目标IP  
    
    	if (SOCKET_ERROR == bind(sock, (const struct sockaddr*)&localif, sizeof(localif)))
    	{
    		int err = WSAGetLastError();//取错误码
    		printf("bind失败错误码为:%d\n", err);
    		closesocket(sock);//释放
    		WSACleanup();//清理网络库
    
    		return 0;
    	}
    
    	char Recvbuf[0x100] = { 0 };
    	int Recvlen = sizeof(localif);
    	while (1)
    	{
    		
    		//用recv简单直接收取消息
    		if (recv(sock, Recvbuf, sizeof(Recvbuf), 0) == SOCKET_ERROR)
    		{
    			int err = WSAGetLastError();//取错误码
    			printf("recv失败错误码为:%d\n", err);
    			continue;
    		}
    
    		//也可以用recvfrom,额外获取发送方IP地址
    		//struct sockaddr_in sa;
    		//int iSaLen = sizeof(sa);
    		//if (recvfrom(sock, Recvbuf, sizeof(Recvbuf), 0, (struct sockaddr*)&sa,&iSaLen) == SOCKET_ERROR)
    		//{
    		//	int err = WSAGetLastError();//取错误码
    		//	printf("recv失败错误码为:%d\n", err);
    		//	continue;
    		//}
    		//printf("%s\n", inet_ntoa(sa.sin_addr));
    
    
    		printf("%s\n", Recvbuf);
    	}
    
    	closesocket(sock);//关闭Socket句柄
    	WSACleanup();//关闭网络库
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    白名单多播源过滤法

    使用ip_mreq_source 添加若干个多播源IP,形成白名单,不再接收其他多播源消息。setsockopt中的选项为IP_ADD_SOURCE_MEMBERSHIP

    	ip_mreq_source mreqs;
    
    	mreqs.imr_interface.S_un.S_addr = inet_addr("127.0.0.1");//本地IP
    	mreqs.imr_multiaddr.S_un.S_addr = inet_addr("234.2.3.4");//多播组IP
    	mreqs.imr_sourceaddr.S_un.S_addr = inet_addr("127.0.0.1");//白名单多播IP
    
    	if (setsockopt(sock, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, (char*)&mreqs, sizeof(ip_mreq_source)) != 0)//加入多播组
    	{
    		printf("setsockopt设置TTL失败!\n");
    		closesocket(sock);//关闭Socket句柄
    		WSACleanup();//关闭网络库
    		return -1;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    如果要移除白名单中的多播IP,则setsockopt中的选项为IP_DROP_SOURCE_MEMBERSHIP

    黑名单多播源过滤法

    使用ip_mreq_source 添加若干个多播源IP,形成黑名单,不再接收黑名单中多播源消息。setsockopt中的选项为IP_ADD_BLOCK_SOURCE。
    取消黑名单使用的选项为:IP_UNBLOCK_SOURCE
    黑名单中的多播源需要存在多播组中,也就是先要用IP_ADD_MEMBERSHIP选项添加到多播组中的多播IP才能添加到黑名单中。

    还有一种设置方式是使用WSAIoctl设置SIO_SET_MULTICAST_FILTER,该选项的数据类型如下:
    https://docs.microsoft.com/en-us/windows/win32/api/ws2ipdef/ns-ws2ipdef-ip_msfilter

    typedef struct ip_msfilter {
      IN_ADDR             imsf_multiaddr;//多播地址
      IN_ADDR             imsf_interface;//本地地址
      MULTICAST_MODE_TYPE imsf_fmode;//黑名单或者白名单
      ULONG               imsf_numsrc;//名单成员数量
      IN_ADDR             imsf_slist[1];//名单具体成员
    } IP_MSFILTER, *PIP_MSFILTER;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    git使用
    最新AI创作系统ChatGPT系统运营源码+支持GPT-4多模态模型
    天翼云Web应用防火墙(边缘云版)支持检测和拦截Apache Spark shell命令注入漏洞
    git使用patch进行补丁操作
    手机扫描二维码的测试用例
    vue 部署到nginx 刷新404
    基础矩阵和本质矩阵
    Python Flask Web:博客登录和注册页面实现代码
    【刷题笔记9.24】LeetCode:对称二叉树
    【论文解读】GPT Understands, Too
  • 原文地址:https://blog.csdn.net/oldmao_2001/article/details/126037870