使用技术:
UDP ipv4 禁止本地回环 允许端口复用 超时等待 限制跳点 Echo
功能描述:
任意endpoint可主动发送多播,也可以收到信息后自动多播
前期准备:
开启操作系统多播广播功能,关闭系统防火墙,使用物理路由还需在路由开启广播多播功能
发送端和接收端运行不分先后 可无限复制多个接收端模拟集群
发送端:
cpp
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#define MULTICAST_GROUP "239.0.0.1"
#define MULTICAST_PORT 55501
//发送端
int main()
{
int server_sockfd;
struct sockaddr_in server_sockaddr, multi_sockaddr, recv_sockaddr;
memset(&server_sockaddr, 0, sizeof(server_sockaddr));
memset(&multi_sockaddr, 0, sizeof(multi_sockaddr));
memset(&recv_sockaddr, 0, sizeof(recv_sockaddr));
socklen_t multi_sockaddr_len = sizeof(multi_sockaddr);
socklen_t server_sockaddr_len = sizeof(server_sockaddr);
socklen_t recv_sockaddr_len = sizeof(recv_sockaddr);
ssize_t send_bytes, recv_bytes;
char send_buf[1024] = "multicast from ubuntu";
char recv_buf[1024] = {0};
char recv_sockaddr_ip[INET_ADDRSTRLEN] = {0};
// 用于加入多播网络
struct ip_mreq mreq = {0};
// 用于超时等待
struct timeval tv = {0};
tv.tv_sec = 1;
tv.tv_usec = 0;
// ipv4 udp
server_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (server_sockfd == -1)
{
perror("socket");
}
// 地址端口复用
int optval = 1;
setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
// 超时等待
setsockopt(server_sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
// 限制跳点
uint8_t ttl = 1;
setsockopt(server_sockfd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
// 禁止本地回环
uint8_t loop = 0;
setsockopt(server_sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));
// 绑定接收端口,与多播端口一致,监听任意地址
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(MULTICAST_PORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
if ((bind(server_sockfd, (struct sockaddr *)&server_sockaddr, server_sockaddr_len)) == -1)
{
perror("bind");
}
// 加入多播组
inet_pton(AF_INET, MULTICAST_GROUP, &mreq.imr_multiaddr.s_addr);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(server_sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
// 设置发送地址为多播组
multi_sockaddr.sin_family = AF_INET;
multi_sockaddr.sin_port = htons(MULTICAST_PORT);
inet_pton(AF_INET, MULTICAST_GROUP, &multi_sockaddr.sin_addr.s_addr);
while (1)
{
sleep(1);
send_bytes = sendto(server_sockfd, send_buf, strlen(send_buf),
0, (struct sockaddr *)&multi_sockaddr, multi_sockaddr_len);
if (send_bytes == -1)
{
perror("sendto");
}
recv_bytes = recvfrom(server_sockfd, recv_buf, sizeof(recv_buf),
0, (struct sockaddr *)&recv_sockaddr, &recv_sockaddr_len);
if (recv_bytes == -1)
{
perror("recvfrom");
}
inet_ntop(AF_INET, &recv_sockaddr.sin_addr, recv_sockaddr_ip, sizeof(recv_sockaddr_ip));
// 收到消息则打印日志
if (recv_bytes > 0)
{
printf("INET_ADDR : %s PORT : %d Message : %s\n", recv_sockaddr_ip, ntohs(recv_sockaddr.sin_port), recv_buf);
memset(recv_buf, 0, sizeof(recv_buf));
memset(&recv_sockaddr, 0, sizeof(recv_sockaddr));
}
}
close(server_sockfd);
return 0;
}
接收端:
cpp
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#define MULTICAST_GROUP "239.0.0.1"
#define MULTICAST_PORT 55501
//接收端
int main()
{
int server_sockfd;
struct sockaddr_in server_sockaddr, multi_sockaddr, recv_sockaddr;
memset(&server_sockaddr, 0, sizeof(server_sockaddr));
memset(&multi_sockaddr, 0, sizeof(multi_sockaddr));
memset(&recv_sockaddr, 0, sizeof(recv_sockaddr));
socklen_t multi_sockaddr_len = sizeof(multi_sockaddr);
socklen_t server_sockaddr_len = sizeof(server_sockaddr);
socklen_t recv_sockaddr_len = sizeof(recv_sockaddr);
ssize_t send_bytes, recv_bytes;
char send_buf[1024] = "multicast from kali";
char recv_buf[1024] = {0};
char recv_sockaddr_ip[INET_ADDRSTRLEN] = {0};
struct ip_mreq mreq = {0};
server_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (server_sockfd == -1)
{
perror("socket");
}
// 禁止本地回环
uint8_t loop = 0;
setsockopt(server_sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));
// 允许端口复用
int optval = 1;
setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
// 绑定地址为监听任意,绑定监听端口为多播端口
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
server_sockaddr.sin_port = htons(MULTICAST_PORT);
if ((bind(server_sockfd, (struct sockaddr *)&server_sockaddr, server_sockaddr_len)) == -1)
{
perror("bind");
}
// 加入多播组
mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_GROUP);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(server_sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
// 向多播组发送消息用
multi_sockaddr.sin_family = AF_INET;
multi_sockaddr.sin_port = htons(MULTICAST_PORT);
inet_pton(AF_INET, MULTICAST_GROUP, &multi_sockaddr.sin_addr.s_addr);
while (1)
{
recv_bytes = recvfrom(server_sockfd, recv_buf, sizeof(recv_buf),
0, (struct sockaddr *)&recv_sockaddr, &recv_sockaddr_len);
if (recv_bytes == -1)
{
perror("recvfrom");
}
inet_ntop(AF_INET, &recv_sockaddr.sin_addr, recv_sockaddr_ip, sizeof(recv_sockaddr_ip));
// 如果收到了消息就打印
if (recv_bytes > 0)
{
printf("INET_ADDR : %s PORT : %d Message : %s\n", recv_sockaddr_ip, ntohs(recv_sockaddr.sin_port), recv_buf);
memset(recv_buf, 0, sizeof(recv_buf));
memset(&recv_sockaddr, 0, sizeof(recv_sockaddr));
}
sleep(1);
// echo一条消息,也可主动发起
send_bytes = sendto(server_sockfd, send_buf, strlen(send_buf),
0, (struct sockaddr *)&multi_sockaddr, multi_sockaddr_len);
if (send_bytes == -1)
{
perror("sendto");
}
}
close(server_sockfd);
return 0;
}