2026/2/19 14:50:46
网站建设
项目流程
网站做视频链接,海外网站seo优化,做网站 工资高吗,合肥建设信息网站这是 epoll 进阶实战的经典案例 —— 基于epoll 边缘触发#xff08;ET#xff09; 非阻塞 IO实现高并发聊天室#xff0c;同时解决 10000 并发连接时的系统限制问题#xff0c;是理解 epoll 在实际项目中落地的核心实践#xff01;一、核心需求与设计思路1. 功能目标支持…这是 epoll 进阶实战的经典案例 —— 基于epoll 边缘触发ET 非阻塞 IO实现高并发聊天室同时解决 10000 并发连接时的系统限制问题是理解 epoll 在实际项目中落地的核心实践一、核心需求与设计思路1. 功能目标支持万级客户端并发连接单进程高效处理所有请求客户端消息实时广播一人发消息全员可接收客户端交互无阻塞输入消息和接收消息互不干扰高并发测试验证模拟 10000 个客户端连接突破系统默认限制。2. 核心架构设计模块核心技术功能职责服务器端epoll ET 模式 非阻塞 IO监听新连接、处理客户端消息、实现消息广播客户端父子进程 管道 epoll子进程读用户输入父进程处理网络通信测试程序批量创建套接字模拟 10000 个客户端连接验证高并发能力3. 核心优势对比 select/poll本方案用 epoll 实现效率与连接数无关事件驱动非轮询支持万级并发突破 select 的 1024 限制边缘触发ET减少冗余事件通知提升性能。二、完整代码实现1. 服务器端Server.cpp#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 listint 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; }2. 客户端Client.cpp#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; }3. 高并发测试程序Tester.cpp#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 vectorint 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, i1, 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; }三、编译运行步骤1. 编译代码# 编译服务器需C11及以上 g Server.cpp -o server # 编译客户端 g Client.cpp -o client # 编译测试程序 g Tester.cpp -o tester2. 运行流程# 第一步启动服务器 ./server # 第二步新开多个终端运行普通客户端聊天 ./client # 第三步测试高并发运行测试程序10000个连接 ./tester四、核心问题解决Too many open files运行测试程序时会出现Too many open files错误原因是Linux 默认限制每个进程最多打开 1024 个文件描述符需修改系统限制1. 修改用户级限制永久生效编辑/etc/security/limits.confsudo vi /etc/security/limits.conf # 添加以下两行 * hard nofile 65535 * soft nofile 65535*对所有用户生效hard硬限制用户无法突破soft软限制用户可临时调高65535最大文件描述符数。2. 修改系统级限制永久生效编辑/etc/sysctl.confsudo vi /etc/sysctl.conf # 添加以下行 fs.file-max65535 # 应用配置 sudo sysctl -p3. 重启系统并验证sudo reboot # 验证限制 ulimit -Hn # 查看硬限制输出65535 ulimit -Sn # 查看软限制输出65535五、代码优化与扩展方向1. 基础优化点增强错误处理将exit(1)改为continue避免单个连接错误导致服务器崩溃调整监听队列listen(listener, 1024)调大队列长度适应高并发连接处理僵尸进程客户端父进程添加waitpid回收子进程资源解决 TCP 粘包添加消息头如长度字段确保消息完整解析。2. 功能扩展方向用户昵称功能客户端连接时发送昵称替代 fd 显示私聊功能扩展协议支持用户名 消息格式的定向发送心跳检测定时发送心跳包清理异常离线客户端跨平台支持使用 libevent 封装 epoll兼容 Windows 的 IOCP。六、总结本案例是epoll 高并发编程的标杆实践核心是 ET 模式 非阻塞 IO实现万级连接的高效处理服务器通过list管理客户端 fd实现消息广播是聊天室的核心逻辑客户端采用父子进程 管道分离输入和网络通信解决 IO 阻塞问题突破系统文件描述符限制是高并发测试的关键需同时修改用户级和系统级配置。