select、poll、epoll
在传入的性能差异是不是体现在,当有新的连接过来,此时需要将新的fd传入到内核中,但是poll/select需要出入整个数组,而epoll方式只需要出入单个fd?
1. select/poll 的情况
-
它们没有内核中“长期保存的集合”。
-
每次调用
select()
或poll()
,你都要把 整个数组/集合(所有要监控的 fd)重新传给内核。 -
不管你只是新增了 1 个连接,还是 100 个连接,调用时都要把全部 fd 拷贝一次。
- 例如原来监控 1000 个 fd,现在来了一个新连接,数组变成 1001 个,调用时需要把这 1001 个 fd 整个传到内核。
- 所以每次调用开销是 O(N),与当前总 fd 数成正比。
2. epoll 的情况
-
内核里有一个持久化的数据结构(红黑树 + 就绪链表)来保存“我关心的 fd”。
-
新连接到来时,只需 调用一次
epoll_ctl(ADD, fd)
把这个单个 fd 注册进去,内核集合就更新了。 -
后续的
epoll_wait
不需要再传全集合,只返回就绪 fd。- 所以新增 fd 的代价是 O(1)(单次 syscall,处理一个 fd),而等待就绪事件的代价是 O(#ready)。
3. 所以性能差异体现在
- select/poll:每次 wait 都要把“全量数组”搬到内核,新增一个 fd 后,每次 wait 的拷贝量都会增加。
- epoll:新增 fd 时只需要告诉内核“再加一个”,以后 wait 的时候不用每次重新拷贝整个集合。
一句话总结
是的,差异就在于:
- select/poll → 新增一个连接后,后续每次调用都要传 整个 fd 集合。
- epoll → 新增一个连接时,只需要传 那个新 fd 一次,之后 wait 再也不用传全集合。
使用水平触发模式时,当被监控的 Socket 上有可读事件发生时,服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束——这里苏醒是什么意思
这个“苏醒”其实是个形象的说法,指的是:
线程在调用 epoll_wait
时会进入阻塞睡眠状态,当监控的 socket 上发生了指定事件(比如可读)时,内核会把这个阻塞的线程唤醒,让它从 epoll_wait
返回。
更精确的过程
-
你调用:
int n = epoll_wait(epfd, events, maxevents, timeout);
- 如果此时没有任何 fd 就绪,当前线程就会进入 睡眠状态,挂起在内核的等待队列里。
-
当某个被监控的 fd 上发生事件(例如 socket 收到数据,可读了):
- 内核把这个事件加入 epoll 的“就绪队列”。
- 内核检查有线程在
epoll_wait
上阻塞,就会唤醒这些线程。
-
线程被唤醒后,
epoll_wait
返回,你得到就绪事件。- 如果是 水平触发(LT),只要缓冲区里还有没读完的数据,fd 就一直处于就绪状态,下一次调用
epoll_wait
又会马上返回(继续“苏醒”)。 - 只有当你把数据读干净(read 返回
EAGAIN
),fd 才会变成未就绪,下次epoll_wait
才会重新睡眠。
- 如果是 水平触发(LT),只要缓冲区里还有没读完的数据,fd 就一直处于就绪状态,下一次调用
“苏醒”的本质
- 内核通过 等待队列(wait queue) 机制把阻塞的线程放眠。
- 当事件发生时,内核调用 wake_up 把线程从等待队列移出,让调度器把它重新放到可运行队列里。
- 线程因此“苏醒”,从
epoll_wait
返回。
用白话类比
- 睡眠:线程打盹,等着内核叫醒。
- 苏醒:内核发现“喂,socket 来数据了!”,拍醒线程说“起来干活!”。
- 水平触发下的不断苏醒:只要桌子上还有没吃完的饭(缓冲区里有数据),内核就会不停叫醒你;只有你把饭吃光(读干净),它才不再打扰你。
那epoll和Reactor之间的关系是什么?是不是Reactor基于epoll实现的
你问得很好 👍,这是很多人一开始都会混淆的地方。
1. epoll 是“机制”,Reactor 是“模式”
-
epoll:是 Linux 内核提供的 I/O 多路复用接口,是一种“系统调用机制”。它的职责就是:
- 高效地监控一批 fd(socket)是否就绪;
- 把就绪事件通知给用户态程序。
👉 属于 操作系统层面的能力。
-
Reactor:是一种 事件驱动编程模型/设计模式。它描述的是如何组织程序结构:
- 一个线程等待事件(
epoll_wait
等),当有事件到来时,分发给对应的“事件处理器(Handler)”来处理。
👉 属于 应用层面的架构模式。
- 一个线程等待事件(
2. 关系
-
Reactor 模式通常用 epoll 来实现(在 Linux 下)。
-
但 Reactor 并不依赖 epoll,它也可以用 select、poll、kqueue(BSD)、IOCP(Windows) 等机制来实现。
-
换句话说:
- epoll = 内核提供的工具。
- Reactor = 用这个工具组织起来的一种设计模式。
3. 举例
比如一个典型的 Reactor 服务器流程:
while (true) {int n = epoll_wait(epfd, events, maxevents, -1); // 等待事件for (i = 0; i < n; i++) {if (events[i].data.fd == listen_fd) {// 有新连接 => 交给 Acceptoraccept_new_connection();} else {// 有数据可读/可写 => 交给对应的 Handlerhandler(events[i].data.fd);}}
}
这里:
epoll_wait
就是 内核机制。- 整个 while 循环里“等待事件-分发-处理”的结构,就是 Reactor 模式。
4. 总结
- epoll 是 操作系统提供的事件通知机制。
- Reactor 是 一种事件驱动的编程模式。
- 在 Linux 里,Reactor 通常是 基于 epoll 来实现的,但在其他系统上也可能基于 poll、kqueue、IOCP 等。
哪些框架是基于Netty?
好问题 👍。Netty 本身就是一个 基于 Reactor 模式、封装了 Java NIO/epoll 的高性能网络通信框架。很多知名框架和中间件都直接或间接使用了 Netty 来处理底层网络通信。
常见基于 Netty 的框架/中间件
RPC / 微服务框架
- Dubbo(阿里开源的 RPC 框架,核心通信基于 Netty)
- gRPC-Java(Google 的 RPC 框架,Java 实现底层默认用 Netty)
- Motan(新浪微博开源的 RPC 框架)
- SOFARPC(蚂蚁金服开源的 RPC 框架,默认基于 Netty)
消息中间件
- RocketMQ(阿里开源的分布式消息队列,通信层用 Netty)
- Kafka(部分组件)(核心基于 NIO,部分工具/生态里使用 Netty)
- ActiveMQ Artemis(底层网络层实现可选 Netty)
数据库相关
- Redis 的部分 Java 客户端(如 Redisson 使用 Netty 管理连接池和通信)
- Cassandra Java 驱动(使用 Netty 处理连接)
- Elasticsearch Java 客户端(低层 IO 使用 Netty)
网关 / 代理
- Zuul 2(Netflix 开源 API Gateway,底层通信是 Netty)
- Spring Cloud Gateway(基于 Spring WebFlux,而 WebFlux 默认基于 Reactor-Netty)
- gRPC-Gateway Java 生态实现
Web 服务器 / HTTP 框架
- Reactor Netty(Spring 官方提供的 Netty 封装,Spring WebFlux 默认用它)
- Vert.x(异步应用框架,默认底层用 Netty)
- Play Framework(早期版本)(部分基于 Netty 的异步处理)
其他
- Finagle(Twitter 开源的异步 RPC 框架,支持多协议,底层 Netty 实现之一)
- HiveMQ(MQTT broker,基于 Netty 实现高并发连接)
- Elasticsearch Transport 层(基于 Netty 处理节点间通信)
一句话总结
Netty = Java 世界的“网络通信基石”。
凡是需要 高并发网络通信 的 Java 框架/中间件,大概率都直接或间接用了 Netty,比如 Dubbo、RocketMQ、Spring Cloud Gateway、Elasticsearch、gRPC-Java。