news 2026/4/15 12:05:08

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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. 突破系统文件描述符限制是高并发测试的关键,需同时修改用户级和系统级配置。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/14 12:09:18

ClickHouse 分片集群备份一致性分析文档

目录标题ClickHouse 分片集群备份一致性分析文档1. 问题背景2. 环境信息2.1 集群配置2.2 Pod 列表2.3 备份配置3. 官方备份方案分析3.1 Altinity clickhouse-backup 工具3.2 工作原理 - FREEZE 机制3.3 ClickHouse 内置 BACKUP/RESTORE 命令4. 分片备份一致性问题4.1 核心问题4…

作者头像 李华
网站建设 2026/4/13 19:22:31

实体商家免费入驻家家有平台,成为联盟商家超详细教程!

想解锁海量客源、沉淀私域流量&#xff0c;还能拿盈利分红&#xff1f;免费入驻家家有联盟商家&#xff0c;零成本享曝光、引客流、增收益&#xff0c;手把手教程帮你快速入驻&#xff0c;轻松盘活店铺生意&#xff01;01成为联盟商家核心优势1.共享平台会员资源&#xff0c;精…

作者头像 李华
网站建设 2026/4/13 17:15:46

材料中心物流信息管理系统的设计与实现

摘  要 近年来&#xff0c;伴随着互联网技术的快速发展和大力应用&#xff0c;各种信息化软件应运而生。当下&#xff0c;随着国内经济由于疫情的影响在全面复苏&#xff0c;各大企业也在注重企业材料成本的管控。在此之前&#xff0c;各大企业针对生产环节中的材料管理都是依…

作者头像 李华
网站建设 2026/4/13 16:28:04

网络基础概念

⽹络基础概念 ⽹络发展 独⽴模式: 计算机之间相互独⽴;&#xff08;在此阶段下&#xff1a;资源无法共享、协作效率低下、运维成本高&#xff09; ⽹络互联: 多台计算机连接在⼀起, 完成数据共享;&#xff08;网络互联实现数据共享优势是打破资源孤岛&#xff0c;但是也带来…

作者头像 李华