组播测试

Posted by 婷 on December 4, 2023 本文总阅读量

简介

上一篇主要讲了很多组播的知识,这次则是直接通过代码实现。

代码分为serverclientserverclient两个设备本身eth0IP是同个网段的,且两者都接在同个交换机下。

server它其实就是个组播源,client则是组播成员,这里直接省去了组播路由器。所以我们使用的多播地址为永久组地址,也就是224.0.0.0-224.0.0.255之间,路由器不会对这个范围的IP包进行转发

image-20231204191059101

代码

server.c,对于server来说比较简单,他只需要往某个多播地址发送UDP报文即可。

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>

#include <arpa/inet.h>
#include <stdio.h>

    

#define MCAST_ADDR "224.0.0.100"		//一个局部连接地址,路由器不进行转发
#define MCAST_PORT 8888
#define MCAST_DATA "MULTICAST TEST DATA"		//多播发送数据
#define MCAST_INTERVAL 5

int main()
{
	struct sockaddr_in mcastAddr;

	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(-1 == sfd)
	{
		perror("socket() failed");
		return -1;
	}
	
	memset(&mcastAddr, 0, sizeof(mcastAddr));
	mcastAddr.sin_family = AF_INET;
	mcastAddr.sin_addr.s_addr = inet_addr(MCAST_ADDR);	//设置多播地址
	mcastAddr.sin_port = htons(MCAST_PORT);			//设置多播端口

	//向多播地址发送数据,不进行网络传输,所以没有bind等一系列操作
	while(1)
	{
		int len = sendto(sfd, MCAST_DATA, sizeof(MCAST_DATA), 0, (struct sockaddr*)&mcastAddr, sizeof(mcastAddr));
		if(len < 0)
		{
			perror("sendto() failed");
			return -2;
		}
		sleep(MCAST_INTERVAL);
	}
	close(sfd);
	return 0;
}

client.c

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>

#include <arpa/inet.h>
#include <stdio.h>

#define MCAST_ADDR "224.0.0.100"		//一个局部连接地址,路由器不进行转发
#define MCAST_PORT 8888
#define MCAST_DATA "MULTICAST TEST DATA"		//多播发送数据
#define MCAST_INTERVAL 5

int main()
{
	char buff[256];
	int ret = 0;
	int loop = 1;
	struct sockaddr_in mcastAddr;
	struct ip_mreq mreq;

	int cfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(-1 == cfd)
	{
		perror("socket() failed");
		return -1;
	}
	
	memset(&mcastAddr, 0, sizeof(mcastAddr));
	mcastAddr.sin_family = AF_INET;
	mcastAddr.sin_addr.s_addr = htonl(INADDR_ANY);	
	mcastAddr.sin_port = htons(MCAST_PORT);			//

	//绑定socket
	ret = bind(cfd, (struct sockaddr*)&mcastAddr, sizeof(mcastAddr));
	if(ret < 0)
	{
		perror("bind failed");
		return -1;
	}

	//设置回环许可
    //默认情况下,当本机发送组播数据到某个网络接口时,在IP层,数据会回送到本地的回环接口,选项IP_MULTICAST_LOOP用于控制数据是否回送到本地的回环接口。参数loop设置为0禁止回送,设置为1允许回送。
	ret = setsockopt(cfd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));
	if(ret < 0)
	{
		perror("setsockopt (IP_MULTICAST_LOOP)failed");
		return -2;
	}
	
    //加入或者退出一个多播组,通过struct ip_mreq控制
	mreq.imr_multiaddr.s_addr = inet_addr(MCAST_ADDR);	//多播地址
	mreq.imr_interface.s_addr = htonl(INADDR_ANY);	//网络接口

	//将本机加入多播组  IP_ADD_MEMBERSHIP
	ret = setsockopt(cfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
	if(ret < 0)
	{
		perror("setsockopt(IP_ADD_MEMBERSHIP) failed");
		return -3;
	}

	while(1)
	{
		memset(buff, 0, sizeof(buff));

		//接受数据
		int size = sizeof(mcastAddr);
		int len = recvfrom(cfd, buff, 256, 0, (struct sockaddr*)&mcastAddr, &size);
		if(len == -1)
		{
			perror("recvfrom failed");
			return -4;
		}

		printf("recv message from server : %s\n", buff);

		sleep(MCAST_INTERVAL);
	}
	//退出多播组  IP_DROP_MEMBERSHIP
	setsockopt(cfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
	close(cfd);
	return 0;
}

client这边就稍微复杂点,大概可以分为这几个步骤

  • 建立一个socket
  • 设置多播的参数,例如超时时间TTL,本地回环许可LOOP等,都是通过setsockopt函数实现
  • 加入多播组,通过setsockopt函数实现
  • 发送和接收数据
  • 从多播组离开,通过setsockopt函数实现

过程

运行程序前,我们先分别用netstat -gip maddr show查看当前的网络信息

server端输出结果如下

image-20231128221403004

image-20231128221306824

client端输出结果如下

image-20231128221326441

image-20231128221336357

这时候运行程序,server

image-20231128000056481

client端收到server的信息

image-20231203164742027

此时我们再用netstat -gip maddr show查看当前的网络信息

server端输出结果如下,其实对于server来说,他是组播源,其实对自己往224.0.0.100组播组发信息是不感知的。从输出的结果看亦是证实了这个观点。

image-20231128221153513

client端输出结果如下,这里显示eth0加入了224.0.0.100组播组。

image-20231128221052242

224.0.0.100的组播MAC地址就是01-00-5e-00-00-64

image-20231128221100572

然后抓包发现用的是IGMPV3协议,抓包数据上传到这里

image-20231128002041873

因为IGMPV3协议相对于IGMPV2协议差别还是很多的,这次只讨论IGMPV2协议,所以这边需要修改下使用的协议版本。

内核中一般对于IGMP的协议版本都是配置为自协商,如果对端不是自协商的状态则会出现版本不匹配而导致多播通信出现问题的情况。不过在这次的网络测试中,serverclient都是配置为自协商的模式。可通过以下接口查看。

cat /proc/sys/net/ipv4/conf/all/force_igmp_version
cat /proc/sys/net/ipv4/conf/default/force_igmp_version
cat /proc/sys/net/ipv4/conf/eth0/force_igmp_version

image-20231128213859183

0代表自动协商,2代表强制为IGMPV23代表强制为IGMPV3

通过以下命令,我们强制改为IGMPV2,改完后需要重新查看下,如果配置还没有生效,需要我们手动执行sync命令同步。

echo 2 > /proc/sys/net/ipv4/conf/all/force_igmp_version
echo 2 > /proc/sys/net/ipv4/conf/default/force_igmp_version
echo 2 > /proc/sys/net/ipv4/conf/eth0/force_igmp_version

image-20231128214528985

两边都修改后,重新运行刚刚的程序并抓包,这次终于抓到了IGMPV2了。

image-20231204190146500

可以看到我们加入组播组跟离开组播组都是只有client端再发送报文。加入的时候,多播地址是224.0.0.100,也就是我们自己加入的地址,而离开的时候,则是用224.0.0.2这个特殊地址,宣告所有组播路由器。

参考链接