浪潮君 的学生作业:
#include // 标准输入输出库
#include // 提供 exit(), malloc(), free()
#include // 提供字符串操作函数,如 strlen(), strncpy(), memcpy()
#include // 提供 close(), pthread_exit()
#include // POSIX 线程库,用于创建和管理线程
#include // 提供 socket 通信接口
#include // 提供 sockaddr_in 结构和端口、地址常量
#include // 提供 inet_ntoa 等 IP 地址转换函数
#define SERVER_PORT 8888 // 服务器监听的 UDP 端口号
#define BUFFER_SIZE 1024 // 缓冲区大小(接收/发送用)
#define PREFIX "回显: " // 回复前缀,回显消息统一加上该前缀
// 线程参数结构体:用于传递每条消息的上下文给线程处理
typedef struct {
int sockfd; // socket 文件描述符
struct sockaddr_in client_addr; // 客户端地址结构体
char message[BUFFER_SIZE]; // 客户端发送的消息内容
ssize_t msg_len; // 客户端消息实际长度(字节数)
} thread_arg_t;
// 线程处理函数:每次接收到客户端数据后就创建线程执行该函数
void *handle_client(void *arg) {
thread_arg_t *client = (thread_arg_t *) arg; // 将 void* 参数转换为 thread_arg_t*
char reply[BUFFER_SIZE]; // 构造回复消息的缓冲区
size_t prefix_len = strlen(PREFIX); // 获取前缀长度
ssize_t reply_len = client->msg_len; // 获取客户端消息长度
// 防止拼接后超出 reply 缓冲区,进行长度截断处理
if (prefix_len + reply_len + 1 > BUFFER_SIZE) {
reply_len = BUFFER_SIZE - prefix_len - 1;
}
// 拼接回显消息:将 "回显: " 前缀和客户端消息合并
memcpy(reply, PREFIX, prefix_len); // 复制前缀
memcpy(reply + prefix_len, client->message, reply_len); // 复制消息内容
reply[prefix_len + reply_len] = '\0'; // 添加字符串结束符
// 打印客户端的 IP、端口和消息内容
printf("收到来自 %s:%d 的数据 :%s\n",
inet_ntoa(client->client_addr.sin_addr), // 将 IP 地址转为字符串
ntohs(client->client_addr.sin_port), // 将端口号从网络字节序转为主机字节序
client->message); // 打印原始消息
// 发送回显消息给客户端
sendto(client->sockfd, reply, prefix_len + reply_len, 0,
(struct sockaddr*) &client->client_addr, sizeof(client->client_addr));
free(client); // 释放线程使用的动态内存
pthread_exit(NULL); // 线程退出
}
// 主函数:负责 socket 初始化、接收数据并启动线程
int main(int argc, char *argv[]) {
int sockfd; // socket 描述符
struct sockaddr_in server_addr, client_addr; // 服务器和客户端地址结构
socklen_t addr_len = sizeof(client_addr); // 客户端地址长度
// 创建 UDP socket,指定为 IPv4 + 无连接数据报类型
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("sock failed"); // 打印错误信息
exit(EXIT_FAILURE); // 创建失败则退出
}
// 初始化服务器地址结构体,清零后设置协议族、IP 和端口
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); // 绑定端口(主机字节序转网络字节序)
// 将 socket 绑定到本地地址和端口
if (bind(sockfd, (struct sockaddr*) &server_addr, sizeof(server_addr)) < 0) {
perror("bind failed"); // 打印绑定失败信息
close(sockfd); // 关闭 socket
exit(EXIT_FAILURE); // 退出程序
}
printf("多线程UDP服务器启动成功,监听端口 %d...\n", SERVER_PORT);
// 主循环:持续接收客户端 UDP 消息,并为每条消息创建一个线程处理
while (1) {
char buffer[BUFFER_SIZE]; // 接收缓冲区
// 接收客户端发送的 UDP 数据报
ssize_t recv_len = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0,
(struct sockaddr*) &client_addr, &addr_len);
if (recv_len < 0) {
perror("recvfrom failed"); // 接收失败,打印错误信息
continue; // 继续接收下一个客户端消息
}
buffer[recv_len] = '\0'; // 添加字符串结束符,确保可打印
// 动态分配线程参数结构体
thread_arg_t *client = malloc(sizeof(thread_arg_t));
if (!client) {
fprintf(stderr, "内存分配失败"); // 打印分配失败信息
continue; // 不处理当前消息,继续等待下一条
}
// 填充线程参数结构体
client->sockfd = sockfd; // socket 描述符
client->client_addr = client_addr; // 客户端地址
client->msg_len = recv_len; // 消息长度
strncpy(client->message, buffer, BUFFER_SIZE); // 拷贝消息内容
client->message[BUFFER_SIZE - 1] = '\0'; // 手动添加结束符,避免溢出
// 创建线程处理当前客户端请求
pthread_t tid;
if (pthread_create(&tid, NULL, handle_client, client) != 0) {
perror("线程创建失败");
free(client); // 创建失败需释放内存
}
// 设置线程为分离状态,自动释放资源(不需要 pthread_join)
pthread_detach(tid);
}
close(sockfd); // 程序退出前关闭 socket
return 0;
}