组播通信是一种基于UDP协议的网络通信方式,它允许发送方将消息同时传递给多个接收方。在组播通信中,发送方和接收方都会加入一个共同的组播组,这个组播组对应一个特定的IP地址,所有加入该组播组的主机都能够接收到发送方发送的消息。组播通信可以有效地减少网络流量和网络负载,因为在传统的点对点通信方式下,每个消息都需要单独传输到每个接收方,而在组播通信中,每个消息只需要传输一次,就可以同时传递给多个接收方。
在使用组播模式时,需要在套接字上使用setsockopt()
函数来设置套接字的IP_MULTICAST_IF
选项,指定本地主机的出站接口地址,用于发送组播数据包。此外,还可以设置IP_ADD_MEMBERSHIP
选项,将套接字加入到一个特定的组播组中,以便接收该组播组中的数据包。
在使用组播模式时需要读者注意,组播模式需要使用特定的IP地址范围,如224.0.0.0~239.255.255.255
,且需要确保组播组内的所有成员都在同一个网络中。同时,组播模式也不保证数据传输的可靠性,因为UDP本身就是无连接的协议,所以需要在应用程序中自行处理数据丢失或重复的情况。
14.5.1 服务端实现
先来实现服务端代码,首先我们定义一个端口号PORT=9999
并定义好组名GROUP="225.1.2.3"
,接着通过调用两次setsockopt
函数,第一次调用指定传入SO_REUSEADDR
参数设置为组播模式,第二次调用指定传入IP_ADD_MEMBERSHIP
用于设置组,经过两次设置服务端将被绑定到GROUP指定的组名上面,并在底部recvfrom
循环等待数据包的到达,当数据包到达后则直接通过sendto
发送一个消息给上线客户端。
#include <winsock.h> #include <iostream>
#pragma comment(lib, "wsock32.lib") #define PORT 9999 #define GROUP "225.1.2.3"
using namespace std;
int main(int argc, char *argv[]) { WSADATA wsaData; struct sockaddr_in addr; int fd; struct ip_mreq mreq;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { std::cout << "初始化失败" << std::endl; return 0; }
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { std::cout << "套接字创建失败" << std::endl; return 0; }
u_int yes = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)) < 0) { std::cout << "设置组播模式失败" << std::endl; return 0; }
memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(PORT);
if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { std::cout << "绑定失败" << std::endl; return 0; }
mreq.imr_multiaddr.s_addr = inet_addr(GROUP); mreq.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)) < 0) { int err = GetLastError(); std::cout << "设置组失败: " << err << std::endl; return 0; }
while (1) { char recv_buffer[4096]; int addrlen = sizeof(addr); int nbytes;
if ((nbytes = recvfrom(fd, recv_buffer, 4096, 0, (struct sockaddr *) &addr, (int *)&addrlen)) < 0) { std::cout << "接收数据包失败" << std::endl; return 0; } recv_buffer[nbytes] = '\0'; std::cout << "接收组播数据包: " << recv_buffer << std::endl;
char send_buffer[4096] = "server mesage"; sendto(fd, send_buffer, strlen(send_buffer), 0, (struct sockaddr *) &addr, sizeof(addr)); }
return 0; }
|
14.5.2 客户端实现
在组播模式中客户端的修改部分很简单,仅仅只需通过socket(AF_INET, SOCK_DGRAM, 0)
函数设置套接字为UDP模式,并填充组名即可,其他通信模式与UDP保持一致。
#include <winsock.h> #include <iostream>
#pragma comment(lib, "wsock32.lib") #define PORT 9999 #define GROUP "225.1.2.3"
using namespace std;
int main(int argc, char *argv[]) { WSADATA wsaData; struct sockaddr_in addr; int fd;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { std::cout << "初始化失败" << std::endl; return 0; }
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { std::cout << "套接字创建失败" << std::endl; return 0; }
memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(GROUP); addr.sin_port = htons(PORT);
while (1) { char send_buffer[4096] = "Hello, World!"; if (sendto(fd, send_buffer, strlen(send_buffer), 0, (struct sockaddr *) &addr, sizeof(addr)) < 0) { std::cout << "发送失败" << std::endl; return 0; }
int addrlen = sizeof(addr); char recv_buffer[4096] = { 0 }; recvfrom(fd, recv_buffer, 4096, 0, (struct sockaddr *) &addr, (int *)&addrlen); std::cout << "接收组播数据包: " << recv_buffer << std::endl; Sleep(1000); } return 0; }
|
读者可自行编译上述代码,运行一个服务端并运行多个客户端即可观察组播收发情况,如下图所示;