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

reactor

为什么有reactor,reactor解决了什么问题?

因为reactor好。

2.1.1讲的传统一请求一线程有下面几个问题

  1. 每个连接消耗一个线程

    • 线程栈通常占用 8MB(ulimit -s 查看),1000 个连接就需要 8GB 内存。

    • 线程切换(上下文切换)需要内核保存/恢复寄存器、页表等,CPU 开销随连接数线性增长。

  2. 阻塞 I/O 浪费 CPU

    • read没有数据时,线程会进入内核睡眠状态,直到数据到达。此时线程无法做任何其他工作。

    • 即使连接上有少量数据,大部分时间线程都在等待,CPU 利用率极低。

    • 占着坑不干活

  3. 无法支撑高并发

    • 在 C10K(一万并发)问题中,创建一万个线程既不现实也不稳定。

因此,必须寻找一种单线程处理大量连接的方法,这就是 Reactor 诞生的原因。

Reactor 模式的核心思想

Reactor 模式的核心是:

使用一个 I/O 多路复用器(如 epoll、poll、select)同时监听多个文件描述符(fd)上的事件。当某个 fd 就绪(可读、可写等)时,epoll返回就绪的 fd 集合,然后 Reactor 调用预先准备好的相应回调函数处理该事件。整个过程是非阻塞的。

其关键组件:

  • 事件多路复用器epoll_wait/select/kqueue

  • 事件处理器:针对不同事件(读、写、异常)的回调函数acceptcb,recv cb, send cb

  • 事件注册/注销接口epoll_ctl recv callback accpet cb

  • 事件循环(Reactor):不断调用多路复用器,获取就绪事件并分发

通过这种方式,一个线程可以同时管理成千上万个连接,且每个连接上的 I/O 操作都不会阻塞线程,因为只有当数据已经在内核缓冲区中时(即可读),回调函数才会被调用;当 socket 发送缓冲区有空间时(即可写),才会触发写回调。

结合代码:

#define CONNECTION_SIZE 1048576 // 支持 100 万连接 struct conn conn_list[CONNECTION_SIZE]; // 每个 fd 对应一个 conn 结构 int epfd; // epoll 文件描述符

reactor设置一个conn结构体为每一个连接保存状态。同时加入到数组当中。该数组一fd为索引,实现o(1)时间的访问。

开始工作后,如果有新的连接进来,fd按照数值放入数组中对应的下标中。结构体内部的recv callback回调函数指向accpet cb。分配clientfd。调用event register函数,分配好revc cb,send cb,调用set event,把该fd添加进epoll中。

进入while循环后,epoll ctl会监听。

如果有,事件会进入就绪链表。直接把链表里的fd放到epoll event里面。

如果是epoll in。那么会调用recv callback回调函数。该函数指向recv cb。接收完数据后会把数据从rbuffer传给wbuffer,该fd的事件会被设置为epoll out。recv调用时,因为 epoll 已经保证该 fd 可读,内核中已经有数据了,recv只是把数据从内核拷贝出来而已。所以recv会立即返回读取到的数据,不会阻塞。

如果是epoll out 事件。调用 send callback 回调函数。该函数指向send cb。发送完数据后fd会被设置为epoll in。而且发送数据时,如果一次send没有发完(例如 socket 发送缓冲区满),传统的阻塞模型会一直等待。在 Reactor 中,我们可以只发送一部分,然后继续保持EPOLLOUT监听,待下次可写时继续发送。通过事件驱动,避免阻塞。

如果没有reactor的话,

  1. 每个连接一个线程:线程数等于连接数,内存爆炸。

  2. 阻塞 I/Orecv会一直等到对方发来数据,该线程在此期间什么都做不了。即使连接空闲,线程也占用内核资源。

  3. 发送阻塞:如果客户端接收窗口为 0,send会阻塞,导致该线程卡死,影响其他连接(虽然其他线程仍可运行,但线程资源已被浪费)。

而在 Reactor 模式下,所有这些阻塞点都变成了事件驱动:没有数据时recv_cb不会被调用;发送缓冲区满时EPOLLOUT不会触发,send_cb不会执行,线程去处理其他就绪的连接。因此,单线程可以高效支撑成千上万的连接。

五、Reactor 模式带来的具体好处(结合代码)

优势在代码中如何体现
高并发conn_list大小 1048576,理论上支持百万连接,而epoll_wait一次返回多个就绪事件。
低内存每个连接只占用一个struct conn(约几 KB,因为缓冲区固定大小),没有额外线程栈。
非阻塞 I/Orecv/send在触发事件时才会在回调中调用,epoll 保证可读/可写,不会阻塞。
动态事件切换set_event(fd, EPOLLOUT, 0)set_event(fd, EPOLLIN, 0)实现读/写状态的精确转换。
业务解耦ws_request不涉及网络 I/O,只操作缓冲区和协议状态,可独立测试和修改。

总结:为什么要有 Reactor?

Reactor 是解决 C10K/C1000K 问题的标准模式。
它通过事件多路复用非阻塞 I/O,使得一个线程能够管理成千上万个连接,避免了传统多线程模型的内存爆炸和上下文切换开销。同时,它将业务逻辑与网络 I/O 分离,让开发者可以专注于协议实现,而底层的高性能并发由 Reactor 框架保证。

在代码中,main+epoll_wait是 Reactor 的心脏;accept_cbrecv_cbsend_cb是负责具体 I/O 的肢体;ws_requesthandshark、编解码函数则是业务大脑。它们各司其职,共同构成了一个简洁但完整的 Reactor 模式 WebSocket 服务器。

如果没有 Reactor,需要为每个 WebSocket 连接创建独立线程,这在小规模测试中或许可行,但在生产环境中面对几千个浏览器客户端时,系统会迅速崩溃。因此,要有Reactor 。

什么是reactor?

reactor是一种事件驱动的网络架构。核心思想是:用一个或尽量少的线程配合io多路复用(epoll)同时监听十万以上的文件描述符事件。当事件连接,可读,可写时,自动调用相应的回调函数来做处理。

ractor什么时候使用?

1.连接数量巨大。如果需要处理成千上万的链接,一线程一连接会消耗大量的资源

2.很多io压根没干活。建立连接后,很多连接都没有数据来回传,只有几个io会传数据,造成资源的浪费。而reactor可以来回切换,有数据就绪的话加进就绪队列。只处理就绪队列。

那么reactor是否真的实现百万并发?reactor的测试:

服务器虚拟机ubuntu 2204.1 4核心 内存7.5g

客户端1:ubuntu 2204.1 2核心 内存4g

客户端2:ubuntu 2204.1 2核心 内存4g

客户端3:ubuntu 1604.6 2核心 内存4g

得到验证:

reactor用在哪里?

  • Web服务器:如Nginx(采用多阶段事件处理模型)、Node.js(基于事件驱动、非阻塞I/O模型)。

  • 反向代理与负载均衡:如Nginx。

  • 应用服务器:如Tomcat、Jetty等,其内部的NIO模块通常都基于Reactor模式实现。

  • 即时通讯(IM)与聊天服务器:需同时处理大量用户的长连接,Reactor是理想选择。

  • 游戏服务器:许多高性能游戏服务器引擎(如Skynet)都基于Reactor模式构建,以支持大量玩家的同时在线和实时交互。

基于reactor 实现了webserver和websocket

使用wrk -c50 -t10 -d10s http://192.168.:2000/来测试

  • wrk:命令行工具,用于发起 HTTP 请求,测量服务器的吞吐量和延迟。

  • -c50:设置并发连接数为50,即模拟 50 个客户端同时与服务器建立连接。

  • -t10:设置线程数为10,wrk 会启动 10 个线程来发送请求(通常每个线程管理一部分连接)。

  • -d10s:设置测试时长为10 秒10s表示 10 seconds)。

  • http://192.168.:2000/:目标服务器的地址,使用 HTTP 协议,IP 为192.168.137.132,端口2000,根路径

用 10 个线程、50 个并发连接,持续 10 秒访问指定的 HTTP 服务(运行在 192.168. 的 2000 端口),最终输出请求总数、每秒请求数(QPS)、延迟分布等性能指标。

线程统计(Thread Stats)

2.1 延迟(Latency)

指标说明
Avg556.37 µs平均每个请求的响应延迟约0.56 毫秒,极低
Stdev147.61 µs标准差较小,延迟分布比较集中
Max8.13 ms最大延迟约 8 毫秒,没有出现长尾延迟
+/- Stdev82.37%约 82% 的请求落在平均值 ±1 个标准差范围内(即 408.76 µs ~ 704.04 µs),大部分请求响应非常稳定

结论:服务延迟表现优秀,处于微秒级到毫秒级,适合对延迟敏感的业务。

2.2 单线程每秒请求数(Req/Sec)

指标说明
Avg8.88k平均每个线程每秒处理8880个请求
Stdev1.29k各线程之间的吞吐量差异不大
Max18.02k某个线程在某个瞬时秒内最高处理了 18020 个请求
+/- Stdev75.92%约 76% 的“线程-秒”采样落在平均值附近

3. 总体统计

  • 总请求数887441(约 88.7 万次请求)

  • 测试时长10.10s(实际耗时略多于设定的 10 秒,通常是 wrk 完成所有请求并收尾的时间)

  • 读取数据量173.50 MB(假设每个响应包体大小约 200 字节左右)

关键性能指标

  • Requests/sec (QPS)87864.91
    整体每秒处理约8.79 万个请求,非常高。

  • Transfer/sec17.18 MB/s
    网络吞吐约17 MB/秒


4. 性能评估

维度表现评价
延迟平均 < 1ms,最大 8ms极低,适合实时性要求高的服务
吞吐量QPS ≈ 8.8 万非常高,常见于高效 HTTP 服务器(如 Nginx、Node.js 异步模型、Rust、Go 等)
稳定性延迟标准差小,最大/平均比值 ≈ 14.6波动小,性能稳定
资源利用10 线程 + 50 连接,处理 88 万请求并发模型合理,没有成为瓶颈
http://www.cnnetsun.cn/news/2114402.html

相关文章:

  • 2026年Geo专家综合能力与执行效果深度评测:AI时代下的信任重构
  • 深度解析开源自动化框架:从图像识别到黑盒测试的完整技术实践
  • Trippy网络诊断工具深度解析:现代网络故障排查的专业利器
  • Outfit字体:现代设计的无声革命者
  • VS Code Dev Containers 优化终极指南(2024年Linux/macOS/WSL3实测版):从22秒冷启动到3.1秒的全链路调优路径
  • Unity GPU 波前(Wavefront / Warp)对 Shader 设计的影响
  • 终极指南:3步快速解密QQ聊天记录数据库
  • 智能办公新方式:OpenClaw 一键部署与配置
  • 3分钟快速部署i茅台自动预约系统:告别手动抢购的Java神器
  • 语义分割调参避坑:你的ASPP模块dilation rate选对了吗?PyTorch实验对比告诉你答案
  • Radeon ProRender Blender插件实战指南:三步实现专业级渲染效果
  • PitchDetect:基于Web Audio API的实时音高检测完整解决方案
  • 只要中一个,就说明你已经找到了对抗加班文化的正确方法
  • VTube Studio终极指南:如何通过API打造个性化虚拟主播体验
  • KUKA iiwa机器人FRI控制实战:手把手教你用Java在Sunrise Workbench 1.7里写第一个实时运动程序
  • 基于MCP协议的AI智能体工具集成平台:Klavis AI架构与应用实践
  • Go语言构建高可用分布式任务调度框架:从Cron到Copaw的实践
  • 5分钟终极指南:如何用untrunc免费快速修复损坏的MP4/MOV视频文件
  • Open WebUI本地部署:构建企业级AI对话平台的技术实践
  • 机器学习入门指南:从零基础到实战应用
  • 3个核心问题,1个终极解决方案:G-Helper让华硕笔记本性能管理变得简单
  • 大语言模型智能体的记忆安全攻防实践
  • Elasticsearch高级搜索实战:多字段相关性得分融合技巧全解
  • 从零构建CNN模型解决CIFAR-10图像分类实战指南
  • 3步掌握Akagi:AI麻将助手让你的雀魂水平突飞猛进
  • 自建AI智能体指挥中心:OpenClaw Dashboard架构与实战
  • 音乐人做编曲伴奏没思路?2026年度甄选5款AI编曲软件总结,解决歌曲的告高质量编曲伴奏的创作难题
  • Cursor Free VIP终极指南:3步永久免费解锁AI编程助手Pro功能
  • 鸣潮120帧解锁终极指南:WaveTools工具箱让游戏体验丝滑如飞
  • 人类增强伦理审查官:软件测试从业者的专业视角与伦理实践框架