当前位置: 首页 > news >正文

30 天自制 C++ 服务器--Day3

高并发还得用epoll

在上一天,我们写了一个简单的echo服务器,但只能同时处理一个客户端的连接。但在这个连接的生命周期中,绝大部分时间都是空闲的,活跃时间(发送数据和接收数据的时间)占比极少,这样独占一个服务器是严重的资源浪费。事实上所有的服务器都是高并发的,可以同时为成千上万个客户端提供服务,这一技术又被称为IO复用。

IO复用和多线程有相似之处,但绝不是一个概念。IO复用是针对IO接口,而多线程是针对CPU。

IO复用的基本思想是事件驱动,服务器同时保持多个客户端IO连接,当这个IO上有可读或可写事件发生时,表示这个IO对应的客户端在请求服务器的某项服务,此时服务器响应该服务。在Linux系统中,IO复用使用select, poll和epoll来实现。epoll改进了前两者,更加高效、性能更好,是目前几乎所有高并发服务器的基石。请读者务必先掌握epoll的原理再进行编码开发。

epoll原理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
总结:

epoll 的核心原理在于改变事件通知模型:它通过在内核使用红黑树高效管理注册的文件描述符,利用回调函数在事件发生时直接将就绪项放入链表,并通过 epoll_wait 仅返回实际就绪的事件列表,辅以 mmap 减少数据拷贝。这种设计彻底解决了 select/poll 在处理大量文件描述符时存在的性能瓶颈(每次调用传递完整集合、线性扫描开销大),使其成为构建现代高性能 Linux 网络服务器(如 Nginx, Redis, Memcached)不可或缺的基础设施。其高效的根源在于将时间复杂度从 O(N) 降低到了 O(1)(或 O(就绪事件数))。

epoll使用

epoll主要由三个系统调用组成:

//int epfd = epoll_create(1024);  //参数表示监听事件的大小,如超过内核会自动调整,已经被舍弃,无实际意义,传入一个大于0的数即可
int epfd = epoll_create1(0);       //参数是一个flag,一般设为0,详细参考man epoll

创建一个epoll文件描述符并返回,失败则返回-1。

epoll监听事件的描述符会放在一颗红黑树上,我们将要监听的IO口放入epoll红黑树中,就可以监听该IO上的事件。

epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);    //添加事件到epoll
epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);    //修改epoll红黑树上的事件
epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);   //删除事件

其中sockfd表示我们要添加的IO文件描述符,ev是一个epoll_event结构体,其中的events表示事件,如EPOLLIN等,data是一个用户数据union:

typedef union epoll_data {void *ptr;int fd;uint32_t u32;uint64_t u64;
} epoll_data_t;
struct epoll_event {uint32_t events;	/* Epoll events */epoll_data_t data;	/* User data variable */
} __EPOLL_PACKED;

epoll默认采用LT触发模式,即水平触发,只要fd上有事件,就会一直通知内核。这样可以保证所有事件都得到处理、不容易丢失,但可能发生的大量重复通知也会影响epoll的性能。如使用ET模式,即边缘触法,fd从无事件到有事件的变化会通知内核一次,之后就不会再次通知内核。这种方式十分高效,可以大大提高支持的并发度,但程序逻辑必须一次性很好地处理该fd上的事件,编程比LT更繁琐。注意ET模式必须搭配非阻塞式socket使用。

非阻塞式socket和阻塞式

在这里插入图片描述
在这里插入图片描述
示例代码:

// 阻塞式服务端伪代码
int client = accept(server_sock); // 阻塞直到新连接
recv(client, buf); // 阻塞直到收到数据
send(client, response); // 阻塞直到发送完成

在这里插入图片描述
在这里插入图片描述

设置方法:

// 设置非阻塞模式
int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

典型工作流:

epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, EPOLLIN); // 注册读事件while (1) {int n = epoll_wait(epfd, events, MAX_EVENTS, -1);for (每个就绪事件) {if (事件 & EPOLLIN) {while ((len = recv(fd, buf)) > 0) {// 处理数据}if (len == -1 && errno == EAGAIN) break; // 数据读完}if (事件 & EPOLLOUT) {// 处理可写事件}}
}

关键差异对比:
在这里插入图片描述
边缘触发 (ET) 与 水平触发 (LT)
当使用 非阻塞 Socket + epoll 时,触发模式的选择至关重要:
在这里插入图片描述
ET 模式最佳实践:

// 边缘触发必须循环读取直到 EAGAIN
while (true) {ssize_t count = read(fd, buf, sizeof(buf));if (count == -1) {if (errno == EAGAIN) break; // 数据已读完// 处理真实错误}if (count == 0) { /* 连接关闭 */ }// 处理数据
}

在这里插入图片描述
在这里插入图片描述

http://www.dtcms.com/a/281892.html

相关文章:

  • NO.6数据结构树|二叉树|满二叉树|完全二叉树|顺序存储|链式存储|先序|中序|后序|层序遍历
  • 【SpringBoot】实战-开发接口-用户-注册
  • 参数检验?非参数检验?
  • 【openbmc3】时间相关
  • 代码随想录算法训练营第五十一天|图论part2
  • 【FreeRTOS】03任务管理
  • 工业相机GigE数据接口的优势及应用
  • django安装、跨域、缓存、令牌、路由、中间件等配置
  • Jenkins全方位CI/CD实战指南
  • LabVIEW Occurrence功能
  • 嵌入式Linux(RV1126)系统定制中的编译与引导问题调试报告
  • 【RTSP从零实践】12、TCP传输H264格式RTP包(RTP_over_TCP)的RTSP服务器(附带源码)
  • 基于WebRTC技术实现一个在线课堂系统
  • el-input 回显怎么用符号¥和变量拼接展示?
  • Spring Boot 解决跨域问题
  • Spring Boot - Spring Boot 集成 MyBatis 分页实现 手写 SQL 分页
  • 日语学习-日语知识点小记-构建基础-JLPT-N3阶段(5):语法+单词
  • Buffer Pool
  • css 如何实现大屏4个占位 中屏2个 小屏幕1个
  • Samba服务器
  • Git版本控制完全指南:从入门到精通
  • 网络编程/Java面试/TCPUDP区别
  • 基于spring boot养老院老人健康监护平台设计与实现
  • SFT:大型语言模型专业化定制的核心技术体系——原理、创新与应用全景
  • docker run elasticsearch 报错
  • JAVA面试宝典 -《分布式ID生成器:Snowflake优化变种》
  • 详解SPFA算法-单源最短路径求解
  • C++ - 仿 RabbitMQ 实现消息队列--sqlite与gtest快速上手
  • 基于springboot+vue的酒店管理系统设计与实现
  • 一叶障目不见森林