网络编程之多播

简介

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

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;

成员①:多播组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;

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

多播实例

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

发送

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<winsock2.h>
#include<ws2ipdef.h>
#include<stdio.h>

#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;
}

接收

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<winsock2.h>
#include<ws2ipdef.h>
#include<stdio.h>

#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;
}

白名单多播源过滤法

使用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;
	}

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

黑名单多播源过滤法

使用ip_mreq_source 添加若干个多播源IP,形成黑名单,不再接收黑名单中多播源消息。setsockopt中的选项为IP_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;
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

oldmao_2000

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

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

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

打赏作者

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

抵扣说明:

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

余额充值