Select、Poll、Epoll 三种机制分析

思考

  1. 高并发 -> socket -> epoll
  2. Linux 提供的系统实现 IO 多路复用器: select、poll(Linux<2.4),epoll(Linux>2.6)

学习目标

  1. epoll 是 Linux 提供的系统实现,核心方法只有三个
  2. epoll 效率高,是因为基于红黑树、双向链表、事件回调机制
  3. redis 的 IO 多路复用,Linux 上用 epoll 进行了实现

socket 是什么?

Socket 是在应用层和传输层之间的一个抽象层,它把 TCP/IP 层复杂的操作抽象为几个简单的接口,供应用层调用实现进程在网络中的通信。Socket 起源于 UNIX,在 UNIX 一切皆文件的思想下,进程间通信就被冠名为文件描述符(file descriptor),Socket 是一种“打开—读/写—关闭”模式的实现,服务器和客户端各自维护一个“文件”,在建立连接打开后,可以向文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。

select、poll

select 是操作系统的提供的内核系统调用函数,通过它可以将一组 FD 传给操作系统,操作系统对这组 FD 进行遍历,当存在 FD 处于数据就绪状态后,将其全部返回给调用方,这样应用程序就可以对已经就绪的 IO 流进行处理了。

// sets的文件描述符的最大值
// fd_set 类型,包含了需要检查是否可读的描述符,输出时表示哪些描述符可读。可为 NULL。
// fd_set 类型,包含了需要检查是否可写的描述符,输出时表示哪些描述符可写。可为 NULL。
// fd_set 类型,包含了需要检查是否出错的描述符,输出时表示哪些描述符出错。可为 NULL。
// struct timeval 类型的结构体,表示等待检查完成的最长时间。
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* errorfds, struct timeval* timeout);

  1. 每次调用 select,都需要把 FD 集合从用户态拷贝到内核态,当 FD 较多时资源开销相对较大
  2. select 并不会只返回就绪的 FD,而是需要用户进程自己一个一个进行遍历找到就绪的 FD
  3. select 最多只能监听 1024 个连接,默认是 1024

poll 机制实际与 select 机制区别不大,只是 poll 机制去除掉了监听连接数 1024 的限制

epoll

epoll 解决了 select 以及 poll 机制的大部分问题,主要体现在以下几个方面:

  1. epoll 创建的红黑树保存所有 FD,没有大小限制,且增删查的复杂度 O(logN)
  2. 基于 callback,利用系统内核触发感兴趣的事件
  3. 就绪列表为双线链表时间复杂度 O(1)
  4. 应用获取到的 FD 都是真实发生 IO 的 FD,与 select 和 poll 需要不断轮询判断是否可用相比,能避免无用的内存拷贝

源码分析

// 调用 epoll_create 建立一个 epoll 对象(在 epoll 文件系统中给这个句柄分配资源);
int epoll_create(int size);

// 调用 epoll_ctl,对指定描述符 FD 执行 op 的绑定操作;把 FD 写入红黑树,同时在内核注册回调函数
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

// 调用 epoll_wait 获取 epfd 上的 io 事件
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

struct eventpoll {
  ...
  /*红黑树的根节点,这棵树中存储着所有添加到epoll中的事件,
  也就是这个epoll监控的事件*/
  struct rb_root rbr;
  /*双向链表rdllist保存着将要通过epoll_wait返回给用户的、满足条件的事件*/
  struct list_head rdllist;
  ...
};

  1. 我们在调用 epoll_create 时,Linux内核除了帮我们在 epoll 文件系统里建了个 file 结点(eventpoll 结构体)
  2. 在内核 cache 里建了个红黑树用于存储以后 epoll_ctl 传来的 socket 外,还会再建立一个 rdllist 双向链表,用于存储准备就绪的事件,当 epoll_wait 调用时,仅仅观察这个 rdllist 双向链表里有没有数据即可。有数据就返回,没有数据就 sleep,等到 timeout 时间到后即使链表没数据也返回。

两种触发模式

EPOLLLTEPOLLET 两种触发模式,LT 是默认的模式,ET 是“高速”模式。

LT (水平触发)模式下,只要这个 FD 还有数据可读,每次 epoll_wait 都会返回它的事件,提醒用户程序去操作

ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无 论 FD 中是否还有数据可读。所以在 ET 模式下,read 一个 FD 的时候一定要把它的 buffer 读光,也就是说一直读到 read 的返回值小于请求值,或者 遇到 EAGAIN 错误。

为什么要有 EPOLLET 触发模式?

如果采用 EPOLLLT 模式的话,系统中一旦有大量你不需要读写的就绪文件描述符,它们每次调用 epoll_wait() 都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率。

而采用 EPOLLET 这种边沿触发模式的话,当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知

这种模式比 LT 效率高,系统不会充斥大量你不关心的就绪文件描述符

其他

[root@iZwz9gkozl3yw55rf29gcoZ code]# cat /proc/sys/fs/file-max
78810

推荐学习

Linux C++网络编程--C/C++视频教程-后端开发-CSDN程序员研修院

C++socket网络编程--http服务器(支持php)实战教学视频--C/C++视频教程-后端开发-CSDN程序员研修院

c++高并发商业级游戏服务器干货【客户端ue4和unity3d】--C/C++视频教程-后端开发-CSDN程序员研修院

netty源码分析与架构介绍--Java视频教程-后端开发-CSDN程序员研修院

C++ 百万并发网络通信引擎架构与实现 (Socket、全栈、跨平台) Version 1.0-c++socket 发送字节,c++定时发送-C/C++视频教程-后端开发-CSDN程序员研修院

Socket编程系列之1:Linux-API网络编程入门实战--C/C++视频教程-后端开发-CSDN程序员研修院

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山中有只拦路虎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值