1、单播、组播和广播
单播是指两个主机点对点之间的通信,广播是主机对整个局域网范围内的主机进行通信,不难看出这两种方式都比较极端,并不符合我们平时的使用,所以我们引入了组播的概念,组播是一种允许一个或多个组播源发送同一个报文到多个接收者的技术。在视频会议,网络游戏中使用的非常广泛。
2、组播地址
组播地址不同于单播地址,它不属于某一个主机,而是属于一组主机,一个组播地址相当于一个群组,需要组播消息的主机都加入这个群组。组播的地址一般是从224.0.0.0开始一直到239.255.255.255。
3、组播的设置
1、接收端
加入群组,因为一个组播地址表示一个群组,所以需要接收组播报文的接收者都加入这个群组。
我们使用setsockopt函数来设置接收端的属性,它的原型如下:
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
我们来看一下它的参数的含义。
sockfd:要设置的套接字描述符,通常是socket()的返回值。
level:指定选项的层级,通常的设置有SOL_SOCKET,表示套接字级别的选项,还有IPPORTO_IP,表示ip层级别的选项(如组播)。
optname:指定要设置的选项名称,常用的有:
SO_REUSEADDR:允许重用本地地址。
IP_ADD_MEMBERSHIP:加入组播组
IP_DROP_MEMBERSHIP:离开组播组
IP_MULTICAST_TTL:设置组播数据包的生存时间
optval:指向选项值的一个const void*类型的指针,这个值的类型和大小取决于 optname
指定的选项。我们在接收端应该让其指向一个struct ip_mreq{}类型的结构体。
optlen:指定optval的指向的数据大小,需要确保这个大小与 optval
中实际数据的大小一致。
2、发送端
设置组播属性,与接收端相同,需要注意的是发送端的optname需要设置为:IP_MULTICAST_IF,optval需要指向一个struct in_addr{}类型的结构体。
4、总结
只要按照UDP的框架,结合组播属性的设置,就可以实现UDP组播通信。只需确保正确的网络配置和有效的代码逻辑,就能完成组播播数据的发送和接收。
5、代码示例
发送端:
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#define GROUP_IP "239.0.0.10"
#define PORT_ID 8080
void* send_message(void* arg)
{
//获取socket
int sockfd = *(int*)arg;
//准备通信地址
struct sockaddr_in server_addr = {};
char buffer[4096] = {};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT_ID);
server_addr.sin_addr.s_addr = inet_addr(GROUP_IP);
//设置组播属性
struct in_addr opt;
inet_pton(AF_INET,GROUP_IP,&opt.s_addr);
setsockopt(sockfd,IPPROTO_IP,IP_MULTICAST_IF,&opt,sizeof(opt));
while(1)
{
printf("请输入要发送的信息:");
fgets(buffer,sizeof(buffer),stdin);
//发送请求
sendto(sockfd,buffer,strlen(buffer),0,(struct sockaddr*)&server_addr,sizeof(server_addr));
}
return NULL;
}
int main(int argc,const char* argv[])
{
//创建socket
int socketfd = socket(AF_INET,SOCK_DGRAM,0);
assert(socketfd!=-1);
//创建线程
pthread_t send_thread;
pthread_create(&send_thread,NULL,send_message,&socketfd);
//等待线程结束
pthread_join(send_thread,NULL);
close(socketfd);
return 0;
}
接收端:
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#define GROUP_IP "239.0.0.10"
#define PORT_ID 8080
void* recv_message(void* arg)
{
//获取套接字描述符
int sockfd = *(int*)arg;
//准备通信地址
struct sockaddr_in recv_addr = {};
recv_addr.sin_family = AF_INET;
recv_addr.sin_port = htons(PORT_ID);
recv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//recv_addr.sin_addr.s_addr = inet_addr(MULTICAST_ADDR);
//绑定
bind(sockfd,(struct sockaddr*)&recv_addr,sizeof(recv_addr));
//加入组播组
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr(GROUP_IP);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
char buffer[4096]= {};
struct sockaddr_in from_addr; // 用于存储发送者地址
socklen_t addr_len = sizeof(from_addr);
while (1)
{
int len = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&from_addr, &addr_len); // 接收消息
if (len > 0)
{
buffer[len] = '\0'; // 确保以NULL结束
printf("收到消息: %s\n", buffer); // 打印接收到的消息
}
}
return NULL; // 线程结束
}
int main(int argc,const char* argv[])
{
//创建套接字
int socketfd = socket(AF_INET,SOCK_DGRAM,0);
assert(socketfd!=-1);
//创建线程
pthread_t recv_thread;
pthread_create(&recv_thread,NULL,recv_message,&socketfd);
//等待子线程
pthread_join(recv_thread,NULL);
close(socketfd);
return 0;
}