Linux 网络编程:epoll 实现聊天室

这是 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. 突破系统文件描述符限制是高并发测试的关键,需同时修改用户级和系统级配置。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1131333.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python 虚拟环境的配置与管理指南

虚拟环境的核心原理 虚拟环境并非重新安装了一套完整的 Python&#xff0c;而是在项目目录下创建了一个包含 Python 解释器副本和独立包管理工具的轻量级目录。激活环境后&#xff0c;系统会将该目录的路径推送到环境变量的最前端&#xff0c;使得终端在调用 Python 指令时优先…

TensorFlow学习系列01 | 实现mnist手写数字识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、前置知识 1、知识总结 概念 作用 归一化 统一数据范围&#xff0c;加速训练 卷积层 提取图像局部特征 池化层 压缩数据&#xff0c;增强鲁棒性 全…

强烈安利8个AI论文网站,自考学生轻松搞定毕业论文!

强烈安利8个AI论文网站&#xff0c;自考学生轻松搞定毕业论文&#xff01; 自考论文的“救星”&#xff1a;AI 工具如何改变你的写作方式 对于自考学生来说&#xff0c;撰写毕业论文往往是一项既耗时又充满挑战的任务。从选题到结构搭建&#xff0c;再到内容撰写和查重降重&…

热电联产在综合能源系统中的选址定容研究Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 &#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室 &#x1f447; 关注我领取海量matlab电子书和数学建模资料 &#x1…

广汽与华为签署全面合作框架协议,深化鸿蒙生态与AI融合

华为ai 2026年1月5日&#xff0c;广汽集团与华为终端在广州签署全面合作框架协议&#xff0c;标志着双方战略合作进一步升级至生态融合新阶段。华为常务董事、终端BG董事长余承东一行到访广汽集团番禺总部&#xff0c;与广汽集团董事长冯兴亚等高层就智能汽车技术演进、产业协同…

别再说Redis是单线程了,这才是它真正的线程模型

“Redis是单线程的。” 这句话你可能听过无数遍。面试官问你Redis为什么快,你脱口而出:"因为它是单线程的,避免了线程切换开销。"面试官满意地点点头,你也觉得自己答对了。 但这个答案,只对了一半。 从Redis 6.0开始,Redis就不再是纯粹的单线程了。它引入了…

为什么WiFi已连接却有感叹号?4种方法修复

在使用电脑连接Wi-Fi时&#xff0c;你是否遇到过无线网络图标旁边出现黄色感叹号的情况&#xff1f;这通常表示网络连接存在问题&#xff0c;导致无法正常上网。下面就为大家分享几种解决方法&#xff0c;帮助你快速恢复网络连接。 方法1&#xff1a;检查路由器与宽带是否正常 …

【开题答辩全过程】以 基于SSM的固定资产管理系统设计与实现为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

基于多能互补的热电联供型微网优化运行Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 &#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室 &#x1f447; 关注我领取海量matlab电子书和数学建模资料 &#x1…

【开题答辩全过程】以 高校失物招领信息管理系统的设计与开发为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

【AlohaMini学习笔记】第二天:初见LeRobot

LeRobot&#xff1a;面向嵌入式AI的开源机器人控制框架 在嵌入式人工智能&#xff08;Embodied AI&#xff09;研究快速发展的当下&#xff0c;机器人控制框架的“易用性、兼容性、低成本”成为推动技术普惠化的关键。 由Hugging Face主导开发的LeRobot正是这样一款面向科研与教…

综合项目实战--电子商城项目

一、摘要本研究设计并实现了一款基于 Linux 平台的轻量级商品展示与查询 Web 系统。系统采用 C 语言开发&#xff0c;通过 Socket 网络编程构建 Web 服务器&#xff0c;集成 SQLite 嵌入式数据库实现数据存储与查询&#xff0c;核心功能涵盖用户登录验证、商品列表展示、关键字…

更高效更智能,华为MatePad 11.5 S解锁高效学习体验

华为鸿蒙系统官网入口 "华为MatePad11.5S搭载超清护眼柔光屏&#xff0c;书写如纸般流畅&#xff1b;HarmonyOS6带来15%流畅度提升与AI智慧功能&#xff0c;从解题到办公全场景重构生产力体验&#xff0c;轻薄机身更适配移动学习。" 临近年末&#xff0c;华为MatePad…

护眼灯品牌排行第一名是谁?权威第一护眼灯品牌曝光,护眼超安心

儿童青少年近视率居高不下&#xff0c;劣质台灯更是雪上加霜&#xff01;权威数据显示&#xff0c;孩子每天被劣质光线照射1小时&#xff0c;半年内近视度数就可能明显上涨。可护眼台灯市场鱼龙混杂&#xff0c;虚假宣传、参数造假层出不穷&#xff0c;家长想给孩子选到靠谱的灯…

AI赋能智能客服:节庆日用品的爆单应对与服务升级核心

一、行业核心矛盾&#xff1a;短周期爆单与场景化适配的双重困境节庆日用品电商以窗花、对联等季节性品类为核心&#xff0c;交易呈现强周期性、爆发式订单特征。节日前30天订单量达平日15-20倍&#xff0c;传统人工客服响应滞后&#xff0c;排队时长超10分钟&#xff0c;用户流…

华为全场景新品发布,智慧时尚的数字生活由此开启

华为主题下载 3月16日&#xff0c;2022华为全屋智能及全场景新品春季发布会正式举办。伴随着全屋Wi-Fi、智能门锁、便携智能音箱HUAWEISoundJoy、HUAWEIP50系列新色、HUAWEInova9SE、HUAWEIMatePadPaper、HUAWEIFreeBuds4E等多款新品发布&#xff0c;华为终端云服务充分发挥…

ToDesk共享屏幕拍照教程,超详细指南

喜欢被拍照但奈何身边的亲友却技术不佳经常无法帮助自己拍摄到令人满意的照片怎么办&#xff1f;一遍一遍重拍不仅有点折腾人并且消磨耐心和情绪也难高效和开心。难道对此就没有其他解决之道了吗&#xff1f;当然不是&#xff0c;通过ToDesk远程控制完成共享屏幕拍照就能轻松搞…

个人数据管理系统

个人数据管理系统一、系统概述该系统基于SpringBoot框架开发&#xff0c;目的是为用户提供个人信息管理的服务。系统功能包括用户登录与权限管理、个人信息管理、数据导入导出、安全与隐私保护等模块。系统设计时注重用户数据的安全性和隐私保护&#xff0c;采用MD5加密存储用户…

智能表格识别技术融合深度学习与计算机视觉,突破传统表格数字化瓶颈

当财务人员面对跨页合并的财务报表&#xff0c;当法务团队需要从数百页合同附件中提取数据&#xff0c;当研究人员试图分析历年格式不一的统计报表时&#xff0c;表格数字化的巨大成本与误差始终难以避免。一种基于深度学习与计算机视觉的表格识别解决方案&#xff0c;直击这一…

【开题答辩全过程】以 农田水井灌溉系统为例,包含答辩的问题和答案

个人简介 一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等 开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。 感谢大家…