简介
上一篇主要讲了很多组播的知识,这次则是直接通过代码实现。
代码分为server
跟client
,server
跟client
两个设备本身eth0
的IP
是同个网段的,且两者都接在同个交换机下。
server
它其实就是个组播源,client
则是组播成员,这里直接省去了组播路由器。所以我们使用的多播地址为永久组地址,也就是224.0.0.0-224.0.0.255之间,路由器不会对这个范围的IP包进行转发。
代码
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 -g
跟ip maddr show
查看当前的网络信息
server
端输出结果如下
client
端输出结果如下
这时候运行程序,server
端
client
端收到server
的信息
此时我们再用netstat -g
跟ip maddr show
查看当前的网络信息
server
端输出结果如下,其实对于server
来说,他是组播源,其实对自己往224.0.0.100组播组发信息是不感知的。从输出的结果看亦是证实了这个观点。
client
端输出结果如下,这里显示eth0
加入了224.0.0.100组播组。
224.0.0.100的组播MAC
地址就是01-00-5e-00-00-64
然后抓包发现用的是IGMPV3
协议,抓包数据上传到这里。
因为IGMPV3
协议相对于IGMPV2
协议差别还是很多的,这次只讨论IGMPV2
协议,所以这边需要修改下使用的协议版本。
内核中一般对于IGMP
的协议版本都是配置为自协商,如果对端不是自协商的状态则会出现版本不匹配而导致多播通信出现问题的情况。不过在这次的网络测试中,server
跟client
都是配置为自协商的模式。可通过以下接口查看。
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
0
代表自动协商,2
代表强制为IGMPV2
,3
代表强制为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
两边都修改后,重新运行刚刚的程序并抓包,这次终于抓到了IGMPV2
的包了。
可以看到我们加入组播组跟离开组播组都是只有client
端再发送报文。加入的时候,多播地址是224.0.0.100,也就是我们自己加入的地址,而离开的时候,则是用224.0.0.2这个特殊地址,宣告所有组播路由器。