实验描述:
1 服务端发起广播
2 服务端设置超时等待10秒 如果没有收到回复 则再次广播
3 客户端启动后 将永久阻塞等待接听广播
4 客户端收到广播后 处理3秒 echo 服务器一条消息
5 服务器打印出这条消息
注意事项:
可复制多个客户端 观察现象
主要用于带有gnu_c 的unix-like系统 其他系统可能要对代码进行改造
服务端:
#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 <sys/errno.h>
// 这是广播地址
#define BROADCAST_IP "192.168.142.255"
#define BROADCAST_PORT 55500
// 这是bind的服务器地址
#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 55500
// 服务器
int main()
{
int server_sockfd;
struct sockaddr_in server_sockaddr, broadcast_sockaddr;
memset(&server_sockaddr, 0, sizeof(server_sockaddr));
memset(&broadcast_sockaddr, 0, sizeof(broadcast_sockaddr));
socklen_t broadcast_sockaddr_len = sizeof(broadcast_sockaddr);
socklen_t server_sockaddr_len = sizeof(server_sockaddr);
ssize_t send_bytes, recv_bytes;
char send_buf[1024] = "server broadcast : How can I help you today ?";
char recv_buf[1024] = {0};
struct timeval tv = {0};
tv.tv_sec = 10;
tv.tv_usec = 0;
server_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
int optval = 1;
// 允许server_sockfd发送广播
setsockopt(server_sockfd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval));
// 无客户端连接的情况下 recvfrom 超时等待10秒
setsockopt(server_sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);
server_sockaddr.sin_port = htons(SERVER_PORT);
server_sockaddr.sin_family = AF_INET;
// 绑定上服务器自己的端口及地址,用于recvfrom
bind(server_sockfd, (struct sockaddr *)&server_sockaddr, server_sockaddr_len);
// 广播地址设置
inet_pton(AF_INET, BROADCAST_IP, &broadcast_sockaddr.sin_addr.s_addr);
broadcast_sockaddr.sin_port = htons(BROADCAST_PORT);
broadcast_sockaddr.sin_family = AF_INET;
while (1)
{
// 广播
send_bytes = sendto(server_sockfd, send_buf, strlen(send_buf), 0,
(struct sockaddr *)&broadcast_sockaddr, broadcast_sockaddr_len);
printf("%s\n", send_buf);
// 等待回复,没收到回复则再次循环
recv_bytes = recvfrom(server_sockfd, recv_buf, sizeof(recv_buf), 0, NULL, NULL);
if (recv_bytes == -1)
{
if (errno == EAGAIN)
{
continue;
}
}
if (recv_bytes > 0)
{
printf("%s\n", recv_buf);
memset(recv_buf, 0, sizeof(recv_buf));
}
}
close(server_sockfd);
return 0;
}
客户端:
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#define BROADCAST_PORT 55500
// 客户端
int main()
{
int client_sockfd;
struct sockaddr_in server_sockaddr, client_sockaddr;
memset(&server_sockaddr, 0, sizeof(server_sockaddr));
memset(&client_sockaddr, 0, sizeof(client_sockaddr));
socklen_t client_sockaddr_len = sizeof(client_sockaddr);
socklen_t server_sockaddr_len = sizeof(server_sockaddr);
ssize_t send_bytes, recv_bytes;
char send_buf[1024] = "kali : give me money !";
char recv_buf[1024] = {0};
// ipv4 udp
client_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
int optval = 1;
// 将client_sockfd绑定的地址及端口设为可重用,应对同一系统多进程多线程客户端
setsockopt(client_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
client_sockaddr.sin_family = AF_INET;
// 监听所有地址包括广播,指定到特定地址例如本机ip则无法监听广播
client_sockaddr.sin_addr.s_addr = INADDR_ANY;
// 监听广播端口
client_sockaddr.sin_port = htons(BROADCAST_PORT);
if (bind(client_sockfd, (struct sockaddr *)&client_sockaddr, client_sockaddr_len) == -1)
{
perror("bind");
}
while (1)
{
printf("-----UCP BROADCAST START-----\n");
// server_sockaddr中包含真实服务器ip地址和广播端口号,不一定是是服务器bind的接收端口号,但这里设成了同一端口
// 只有收到广播 recvfrom才返回
recv_bytes = recvfrom(client_sockfd, recv_buf, sizeof(recv_buf), 0,
(struct sockaddr *)&server_sockaddr, &server_sockaddr_len);
printf("%s\n", recv_buf);
for (size_t i = 3; i > 0; i--)
{
printf("%zu\n", i);
sleep(1);
}
// 因为获得了服务器的真实ip和同一端口,所以可以直接回复
send_bytes = sendto(client_sockfd, send_buf, strlen(send_buf), 0,
(struct sockaddr *)&server_sockaddr, server_sockaddr_len);
printf("%s\n", send_buf);
printf("-----UCP BROADCAST END-------\n\n");
}
close(client_sockfd);
return 0;
}