概述
在移动互联网时代,随着多媒体应用的日益普及,如何高效地将数据传输给多个接收者成为了网络通信领域的一个重要课题。多播(英文为Multicast)作为一种高效的网络通信方式,可以将数据同时发送到多个接收者,而不需要为每个接收者单独建立连接。
与单播(英文为Unicast)相比,多播减少了网络中的数据包复制,从而降低了带宽消耗。与广播(英文为Broadcast)相比,多播仅向那些明确表示希望接收数据的主机发送数据,而不是局域网内的所有主机。多播技术因其高效的数据传输特性,特别适合视频会议、在线教育、远程培训、直播服务等应用场景。
在IPv4地址中,224.0.0.0至239.255.255.255被保留用于多播。IPv6则有更大的多播地址空间,从FF00::/8开始。
多播组
多播不是直接面向特定主机的,而是面向一组主机。任何想要接收特定多播组数据的主机,都可以加入该组。当数据发送到一个特定的多播地址时,所有加入了这个多播组的主机都能接收到这些数据。多播组的成员身份是动态的,设备可以根据需要加入或离开多播组。
1、加入多播组。要让设备能够接收多播数据,首先需要告诉网络层该设备希望加入某个多播组,这是通过设置套接字选项来实现的。
(1)对于IPv4,使用setsockopt函数设置IP_ADD_MEMBERSHIP选项。
(2)对于IPv6,使用setsockopt函数设置IPV6_JOIN_GROUP选项。
具体如何加入,可参考下面的示例代码。
// 对于IPv4
struct ip_mreq mreq;
// 多播地址
mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.1");
// 任意接口,也可指定某个特定接口
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)) < 0)
{
cout << "加入多播组失败" << endl;
}
// 对于IPv6
struct ipv6_mreq mreq6;
// 多播地址
mreq6.ipv6mr_multiaddr = in6addr_any;
// 任意接口
mreq6.ipv6mr_interface = 0;
if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6, sizeof(mreq6)) < 0)
{
cout << "加入多播组失败" << endl;
}
2、离开多播组。当不再需要接收多播数据时,设备可以离开多播组以释放资源。
(1)对于IPv4,使用setsockopt函数设置IP_DROP_MEMBERSHIP选项。
(2)对于IPv6,使用setsockopt函数设置IPV6_LEAVE_GROUP选项。
具体如何离开,可参考下面的示例代码。
// 对于IPv4
if (setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char *)&mreq, sizeof(mreq)) < 0)
{
cout << "离开多播组失败" << endl;
}
// 对于IPv6
if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &mreq6, sizeof(mreq6)) < 0)
{
cout << "离开多播组失败" << endl;
}
3、成员资格查询。在某些情况下,我们可能需要查询一个网络接口是否已经加入了特定的多播组。
(1)对于IPv4,使用setsockopt函数设置IP_MULTICAST_IF选项。
(2)对于IPv6,使用setsockopt函数设置IPV6_MULTICAST_IF选项。
具体如何查询,可参考下面的示例代码。
// 对于IPv4,查询成员资格
struct ip_mreqn mreqn;
socklen_t len = sizeof(mreqn);
if (getsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &mreqn, &len) < 0)
{
cout << "获取多播接口失败" << endl;
}
else
{
// 判断是否加入了多播组
if (mreqn.mr_multiaddr.s_addr == INADDR_ANY)
{
cout << "没有加入任何多播组" << endl;
}
else
{
cout << "已加入多播组, 地址为: " << inet_ntoa(mreqn.mr_multiaddr) << endl;
}
}
// 对于IPv6,查询成员资格
struct ipv6_mreq mreq6;
socklen_t len = sizeof(mreq6);
if (getsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &mreq6, &len) < 0)
{
cout << "获取多播接口失败" << endl;
}
else
{
// 判断是否加入了多播组
if (memcmp(&mreq6.ipv6mr_multiaddr, &in6addr_any, sizeof(in6addr_any)) == 0)
{
cout << "没有加入任何多播组" << endl;
}
else
{
char pszAddr[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &mreq6.ipv6mr_multiaddr, pszAddr, sizeof(pszAddr));
cout << "已加入多播组, 地址为: " << pszAddr << endl;
}
}
收发数据
发送多播数据与发送单播数据类似,只需要指定多播地址作为目标即可。根据多播的特性可以知道,发送方并不关心哪些主机实际上接收了数据。具体如何发送,可参考下面的示例代码。
// 设置多播地址
struct sockaddr_in dest;
dest.sin_family = AF_INET;
dest.sin_port = htons(PORT);
inet_pton(AF_INET, "224.0.0.1", &dest.sin_addr);
// 发送数据
const char *pszMsg = "Hello, Hope Wisdom";
sendto(sockfd, pszMsg, strlen(pszMsg), 0, (struct sockaddr *)&dest, sizeof(dest));
接收多播数据同样与接收单播数据相似,但需要绑定到一个特定的端口,并且通常绑定到一个任意IP地址(INADDR_ANY)。这意味着,我们可以从任何网络接口接收数据。具体如何接收,可参考下面的示例代码。
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = INADDR_ANY;
// 绑定套接字到端口
bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
// 接收数据
char pszBuf[BUFSIZE] = {0};
int nRecvedBytes = recvfrom(sockfd, pszBuf, BUFSIZE, 0, NULL, NULL);
cout << "Recved data: " << pszBuf<< endl;