葫芦岛市网站建设_网站建设公司_React_seo优化
2026/1/8 21:22:18 网站建设 项目流程

这是 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 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; }

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 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; }

三、编译运行步骤

1. 编译代码

# 编译服务器(需C++11及以上) g++ Server.cpp -o server # 编译客户端 g++ Client.cpp -o client # 编译测试程序 g++ Tester.cpp -o tester

2. 运行流程

# 第一步:启动服务器 ./server # 第二步(新开多个终端):运行普通客户端聊天 ./client # 第三步(测试高并发):运行测试程序(10000个连接) ./tester

四、核心问题解决:Too many open files

运行测试程序时,会出现Too many open files错误,原因是Linux 默认限制每个进程最多打开 1024 个文件描述符,需修改系统限制:

1. 修改用户级限制(永久生效)

编辑/etc/security/limits.conf

sudo vi /etc/security/limits.conf # 添加以下两行 * hard nofile 65535 * soft nofile 65535
  • *:对所有用户生效;
  • hard:硬限制(用户无法突破);
  • soft:软限制(用户可临时调高);
  • 65535:最大文件描述符数。

2. 修改系统级限制(永久生效)

编辑/etc/sysctl.conf

sudo vi /etc/sysctl.conf # 添加以下行 fs.file-max=65535 # 应用配置 sudo sysctl -p

3. 重启系统并验证

sudo reboot # 验证限制 ulimit -Hn # 查看硬限制,输出65535 ulimit -Sn # 查看软限制,输出65535

五、代码优化与扩展方向

1. 基础优化点

  • 增强错误处理:将exit(1)改为continue,避免单个连接错误导致服务器崩溃;
  • 调整监听队列listen(listener, 1024)调大队列长度,适应高并发连接;
  • 处理僵尸进程:客户端父进程添加waitpid,回收子进程资源;
  • 解决 TCP 粘包:添加消息头(如长度字段),确保消息完整解析。

2. 功能扩展方向

  • 用户昵称功能:客户端连接时发送昵称,替代 fd 显示;
  • 私聊功能:扩展协议,支持@用户名 消息格式的定向发送;
  • 心跳检测:定时发送心跳包,清理异常离线客户端;
  • 跨平台支持:使用 libevent 封装 epoll,兼容 Windows 的 IOCP。

六、总结

  1. 本案例是epoll 高并发编程的标杆实践,核心是 ET 模式 + 非阻塞 IO,实现万级连接的高效处理;
  2. 服务器通过list管理客户端 fd,实现消息广播,是聊天室的核心逻辑;
  3. 客户端采用父子进程 + 管道分离输入和网络通信,解决 IO 阻塞问题;
  4. 突破系统文件描述符限制是高并发测试的关键,需同时修改用户级和系统级配置。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询