浪潮君 的学生作业:
#include // 标准输入输出库
#include // 提供 exit() 函数
#include // 提供字符串处理函数,如 memset、strlen、memcpy
#include // 提供 close() 等 POSIX API
#include // 提供 socket 套接字函数
#include // 提供 sockaddr_in 结构
#include // 提供 inet_ntoa 等网络地址转换函数
#define SERVER_PORT 8888 // 服务器监听的 UDP 端口
#define BUFFER_SIZE 1024 // 通信缓冲区大小
#define PREFIX "回显:" // 回复消息前缀,用于构造回显内容
int main() {
int sockfd; // socket 文件描述符
struct sockaddr_in server_addr; // 服务器地址结构体
struct sockaddr_in client_addr; // 客户端地址结构体
char buffer[BUFFER_SIZE]; // 接收客户端数据的缓冲区
socklen_t addr_len = sizeof(client_addr); // 客户端地址结构长度,recvfrom 会修改该值
// 创建 UDP socket,使用 IPv4 和数据报类型
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket 创建失败");
exit(EXIT_FAILURE);
}
// 清空服务器地址结构体内容
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET; // 设置地址族为 IPv4
server_addr.sin_addr.s_addr = INADDR_ANY; // 接收所有网卡上的数据
server_addr.sin_port = htons(SERVER_PORT); // 设置监听端口(主机字节序转网络字节序)
// 清空保留字段,确保结构体干净(对齐结构、兼容某些系统)
memset(&(server_addr.sin_zero), 0, 8);
// 将 socket 与本地地址绑定
if (bind(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
perror("bind 绑定失败");
close(sockfd);
exit(EXIT_FAILURE);
}
// 打印服务器启动提示
printf("UDP服务器启动成功,监听端口 %d...\n", SERVER_PORT);
// 无限循环,持续接收和响应客户端消息
while (1) {
// 从客户端接收 UDP 数据报,最多接收 BUFFER_SIZE - 1 字节,预留给 \0
ssize_t recv_len = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0,
(struct sockaddr *) &client_addr, &addr_len);
// 接收失败时输出错误信息,继续下一次循环
if (recv_len < 0) {
perror("recvfrom 接收失败");
continue;
}
// 添加字符串结束符,确保 buffer 是合法的 C 字符串
buffer[recv_len] = '\0';
// 打印客户端地址和收到的消息内容
printf("收到来自 %s:%d 的数据:%s\n",
inet_ntoa(client_addr.sin_addr), // 将 IP 地址转换为字符串
ntohs(client_addr.sin_port), // 将端口号从网络字节序转为主机字节序
buffer);
// 构造回复消息
char reply[BUFFER_SIZE]; // 回显缓冲区
size_t prefix_len = strlen(PREFIX); // 获取前缀长度
// 若前缀 + 接收数据 + '\0' 总长度超过缓冲区上限,则截断
if (prefix_len + recv_len + 1 > BUFFER_SIZE) {
fprintf(stderr, "回显消息过长,已被截断\n");
recv_len = BUFFER_SIZE - prefix_len - 1; // 调整接收长度以防止越界
buffer[recv_len] = '\0'; // 重新添加结束符
}
// 复制前缀到回复缓冲区的起始位置
memcpy(reply, PREFIX, prefix_len);
// 将客户端发来的消息内容追加到前缀后面,并复制结束符 '\0'
memcpy(reply + prefix_len, buffer, recv_len + 1);
// 向客户端发送回显消息(前缀 + 原始消息),总长度为 prefix_len + recv_len 字节
if (sendto(sockfd, reply, prefix_len + recv_len, 0,
(struct sockaddr *) &client_addr, addr_len) < 0) {
perror("sendto 发送失败");
}
}
close(sockfd);
return 0;
}