嵌入式定时器计时技巧:用有符号数省略溢出判断的底层逻辑与实践
2026/1/9 0:31:40
这是 epoll 进阶实战的经典案例 —— 基于epoll 边缘触发(ET)+ 非阻塞 IO实现高并发聊天室,同时解决 10000 并发连接时的系统限制问题,是理解 epoll 在实际项目中落地的核心实践!
| 模块 | 核心技术 | 功能职责 |
|---|---|---|
| 服务器端 | epoll ET 模式 + 非阻塞 IO | 监听新连接、处理客户端消息、实现消息广播 |
| 客户端 | 父子进程 + 管道 + epoll | 子进程读用户输入,父进程处理网络通信 |
| 测试程序 | 批量创建套接字 | 模拟 10000 个客户端连接,验证高并发能力 |
对比 select/poll,本方案用 epoll 实现:
#include <stdio.h> #include <iostream> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <sys/epoll.h> #include <list> #include <string.h> #include <time.h> using namespace std; #define EPOLL_SIZE 10000 list<int> clients_list; // 存储在线客户端fd // 消息处理与广播函数 int handle_message(int client) { char buf[BUFSIZ], msg[BUFSIZ]; int len = read(client, buf, BUFSIZ); if (len < 0) { perror("recv failed"); exit(1); } if (len == 0) { // 客户端关闭连接 close(client); clients_list.remove(client); return len; } // 聊天室仅一人时的提示 if (clients_list.size() == 1) { const char* tip = "聊天室只有你一人了哦!"; write(client, tip, strlen(tip) + 1); return len; } // 构造广播消息并转发 sprintf(msg, "客户 #%d>> %s", client, buf); int msg_len = strlen(msg) + 1; for (auto it = clients_list.begin(); it != clients_list.end(); it++) { if (*it != client) { write(*it, msg, msg_len); } } return len; } int main() { struct sockaddr_in addr, their_addr; addr.sin_family = PF_INET; addr.sin_port = htons(8000); addr.sin_addr.s_addr = INADDR_ANY; socklen_t socklen = sizeof(their_addr); struct epoll_event events[EPOLL_SIZE]; int epfd, epoll_events_count; char message[BUFSIZ]; // 1. 创建监听套接字并设为非阻塞 int listener = socket(PF_INET, SOCK_STREAM, 0); if (listener < 0) { perror("socket failed"); exit(1); } fcntl(listener, F_SETFL, fcntl(listener, F_GETFD, 0) | O_NONBLOCK); // 2. 绑定+监听 if (bind(listener, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind failed"); exit(1); } if (listen(listener, 1024) < 0) { // 调大监听队列 perror("listen failed"); exit(1); } // 3. 初始化epoll,添加监听套接字(ET模式) epfd = epoll_create(EPOLL_SIZE); struct epoll_event ev; ev.events = EPOLLIN | EPOLLET; ev.data.fd = listener; if (epoll_ctl(epfd, EPOLL_CTL_ADD, listener, &ev) < 0) { perror("epoll_ctl failed"); exit(1); } printf("epoll聊天室服务器启动,监听8000端口...\n"); while (1) { // 等待事件触发 epoll_events_count = epoll_wait(epfd, events, EPOLL_SIZE, -1); if (epoll_events_count < 0) { perror("epoll_wait failed"); exit(1); } clock_t tStart = clock(); // 处理所有触发的事件 for (int i = 0; i < epoll_events_count; i++) { // 场景1:新客户端连接 if (events[i].data.fd == listener) { int client = accept(listener, (struct sockaddr*)&their_addr, &socklen); fcntl(client, F_SETFL, fcntl(client, F_GETFD, 0) | O_NONBLOCK); ev.data.fd = client; epoll_ctl(epfd, EPOLL_CTL_ADD, client, &ev); clients_list.push_back(client); // 发送欢迎消息 sprintf(message, "欢迎加入聊天室,你的ID是[%d]", client); write(client, message, strlen(message) + 1); printf("新客户端加入,ID=%d,当前在线:%ld\n", client, clients_list.size()); } // 场景2:客户端发消息 else { handle_message(events[i].data.fd); } } // 打印性能统计 printf("处理%d个事件,耗时:%.2fms\n", epoll_events_count, (double)(clock() - tStart) * 1000 / CLOCKS_PER_SEC); } close(listener); close(epfd); return 0; }#include <stdio.h> #include <iostream> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <sys/epoll.h> #include <string.h> #include <signal.h> using namespace std; #define BUFFER_SIZE BUFSIZ char message[BUFFER_SIZE]; int continue_to_work = 1; int main() { struct sockaddr_in addr; addr.sin_family = PF_INET; addr.sin_port = htons(8000); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 1. 创建套接字并连接服务器 int sock = socket(PF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("socket failed"); exit(1); } if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("connect failed"); exit(1); } // 2. 创建管道,用于父子进程通信 int pipe_fd[2]; if (pipe(pipe_fd) < 0) { perror("pipe failed"); exit(1); } // 3. 初始化epoll,监听套接字和管道读端 int epfd = epoll_create(2); struct epoll_event ev, events[2]; ev.events = EPOLLIN | EPOLLET; ev.data.fd = sock; epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &ev); ev.data.fd = pipe_fd[0]; epoll_ctl(epfd, EPOLL_CTL_ADD, pipe_fd[0], &ev); // 4. fork父子进程 int pid = fork(); if (pid < 0) { perror("fork failed"); exit(1); } else if (pid == 0) { // 子进程:读取用户输入,写入管道 close(pipe_fd[0]); printf("输入消息发送(输入exit退出):\n"); while (continue_to_work) { fgets(message, BUFFER_SIZE, stdin); message[strlen(message) - 1] = 0; // 去掉换行符 if (strncasecmp(message, "exit", 4) == 0) { continue_to_work = 0; } else { write(pipe_fd[1], message, strlen(message) + 1); } } } else { // 父进程:监听epoll,处理网络消息 close(pipe_fd[1]); while (continue_to_work) { int epoll_events_count = epoll_wait(epfd, events, 2, -1); for (int i = 0; i < epoll_events_count; i++) { if (events[i].data.fd == sock) { // 接收服务器消息 int res = read(sock, message, BUFFER_SIZE); if (res == 0) { printf("服务器已关闭\n"); continue_to_work = 0; } else { printf("%s\n", message); } } else { // 读取管道消息,发送到服务器 read(pipe_fd[0], message, BUFFER_SIZE); write(sock, message, strlen(message) + 1); } } } kill(pid, SIGTERM); // 终止子进程 } close(sock); close(epfd); return 0; }#include <stdio.h> #include <iostream> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <vector> #include <string.h> #include <time.h> using namespace std; #define EPOLL_SIZE 10000 vector<int> list_of_clients; int main() { struct sockaddr_in addr; addr.sin_family = PF_INET; addr.sin_port = htons(8000); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); char message[BUFSIZ]; clock_t tstart = clock(); // 批量创建10000个客户端连接 for (int i = 0; i < EPOLL_SIZE; i++) { int sock = socket(PF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("socket failed"); exit(1); } if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("connect failed"); exit(1); } list_of_clients.push_back(sock); // 读取欢迎消息 read(sock, message, BUFSIZ); printf("客户端%d:%s\n", i+1, message); } // 关闭所有连接 for (auto fd : list_of_clients) { close(fd); } double duration = (double)(clock() - tstart) / CLOCKS_PER_SEC; printf("测试完成:创建%d个连接,耗时%.2f秒\n", EPOLL_SIZE, duration); return 0; }# 编译服务器(需C++11及以上) g++ Server.cpp -o server # 编译客户端 g++ Client.cpp -o client # 编译测试程序 g++ Tester.cpp -o tester# 第一步:启动服务器 ./server # 第二步(新开多个终端):运行普通客户端聊天 ./client # 第三步(测试高并发):运行测试程序(10000个连接) ./tester运行测试程序时,会出现Too many open files错误,原因是Linux 默认限制每个进程最多打开 1024 个文件描述符,需修改系统限制:
编辑/etc/security/limits.conf:
sudo vi /etc/security/limits.conf # 添加以下两行 * hard nofile 65535 * soft nofile 65535*:对所有用户生效;hard:硬限制(用户无法突破);soft:软限制(用户可临时调高);65535:最大文件描述符数。编辑/etc/sysctl.conf:
sudo vi /etc/sysctl.conf # 添加以下行 fs.file-max=65535 # 应用配置 sudo sysctl -psudo reboot # 验证限制 ulimit -Hn # 查看硬限制,输出65535 ulimit -Sn # 查看软限制,输出65535exit(1)改为continue,避免单个连接错误导致服务器崩溃;listen(listener, 1024)调大队列长度,适应高并发连接;waitpid,回收子进程资源;@用户名 消息格式的定向发送;list管理客户端 fd,实现消息广播,是聊天室的核心逻辑;