◆ 博主名称 小此方-CSDN博客大家好欢迎来到小此方的博客。⭐️网络系列个人专栏 【主题曲】计算机网络⭐️此方的GitHub github_此方⭐️我们思考 (Rethink) · 我们重建 (Rebuild) · 我们记录 (Record)文章目录概要序論一、从 select 的痛点走向 poll接口设计、结构体与事件机制1.1 poll 的函数原型1.2 深入理解 struct pollfd 结构体1.3 常用的事件类型1.4 poll 是如何解决 select 的痛点的1.4.1 问题一输入输出参数分离无需重复重置1.4.2 问题二彻底打破数量上限1.4.3 实用小技巧忽略某个 fd1.5 实战插曲如何查看一个进程的文件描述符二、poll 的优缺点分析与性能瓶颈2.1 poll 相比 select 的核心优势2.2 无法逃避的性能瓶颈poll 的缺点2.3 总结与过渡为什么需要 epoll概要序論Hello大家好我是此方。上一篇我们讲解了Select本文我们来讲解Poll看看Poll在Select的基础上又有哪些优化。一、从 select 的痛点走向 poll接口设计、结构体与事件机制在上一节中我们深度剖析了 select 的机制。虽然它是 IO 多路复用的老大哥但它的痛点也极其明显每次调用都要手动重置 fd_set 集合而且还受限于 fd_set 的位图大小无法支撑海量并发。为了解决这些问题poll横空出世了。poll的作用和定位与select一致它依然只负责等一次可以等待多个 fd事件就绪后就可以对上层进行事件通知。但它在接口和底层数据结构的设计上做了一次极其优雅的升级。1.1 poll 的函数原型我们来看看poll的函数原型#includepoll.hintpoll(structpollfd*fds,nfds_tnfds,inttimeout);这个接口比select清爽太多了只有三个参数fds是数组的起始地址。nfds是数组元素的个数。timeout直接以毫秒为单位。如果传入 0 表示非阻塞传入 -1 表示阻塞等待。1.2 深入理解 struct pollfd 结构体poll抛弃了select那种反人类的位图bitmap操作转而使用了一个结构体数组。我们来看看这个结构体structpollfd{intfd;/* file descriptor */shortevents;/* requested events */shortrevents;/* returned events */};这个设计极其绝妙把输入和输出完美分离了events是事件位图代表用户告诉内核你要帮我关心fd上面的events事件。revents也是事件位图代表poll成功返回时内核告诉用户你让我关心的fd上面的事件哪些已经就绪了调用的时候fd和events有效成功返回时fd和revents有效。1.3 常用的事件类型在poll的世界里事件类型非常丰富但作为初学者别的我们先不要管我们就记住这两个核心事件就够了POLLIN数据包括普通数据和优先数据可读。POLLOUT数据包括普通数据和优先数据可写。那要怎么设置和检查呢设置监听读事件events | POLLIN检查是否就绪revents POLLIN1.4 poll 是如何解决 select 的痛点的对比selectpoll到底帮我们解决了哪些恶心的问题1.4.1 问题一输入输出参数分离无需重复重置poll的输入输出参数彻底分离了所以你不用再在 poll 之前进行参数重置了你只需要初始化一次events如果下次还要复用这个数组直接再次调用poll即可。revents每次会被内核重新覆盖不会影响下一次的监听逻辑。1.4.2 问题二彻底打破数量上限poll 等待的 fd 个数没有上限为什么呢因为上限问题与poll这个系统调用本身无关了。原本select是直接在内核里写死了fd_set大小限制了上限。但是现在poll的上限直接由用户自己决定用户甚至可以直接 malloc 开辟出来的动态数组传递进去。你想监听一万个还是十万个 fd只需要把数组开得足够大即可。1.4.3 实用小技巧忽略某个 fd如果某天你不想关心数组中的某个 fd 了但又不想去移动数组元素有个非常简单的办法把该结构体里的fd设置为-1。在内核中如果遇到fd -1内核就不关心这类 fd 的 events。1.5 实战插曲如何查看一个进程的文件描述符在编写poll服务器的代码时我们经常需要查看底层到底打开了哪些 fd。在 Linux 终端下你可以通过进程号来直接查看# 假设你的进程号是 235849ls/proc/235849/fd-l执行后你能清晰地看到该进程打开的socket文件描述符、/dev/pts终端描述符等。这对于排查网络 IO 程序的资源泄漏有着极大的帮助。既然poll已经解决了select的两大痛点那它是不是就是完美的了下一节我们将把select改成poll服务器并看看它依然存在的隐患。我写了一个小Demo可供参考Selectepollpoll三大多路转接工具进行测试的demo代码二、poll 的优缺点分析与性能瓶颈我们在上一节中了解了poll的基本用法也知道了它是为了解决select的痛点而诞生的。但在实际工程中没有任何一种模型是完美的。我们来系统性地梳理一下poll的优势与它无法逃避的物理限制。2.1 poll 相比 select 的核心优势不同于 select 使用三个位图来表示三个 fdset 的方式poll 使用一个pollfd指针实现。这带来了几个非常直观的好处pollfd 结构包含了要监视的 event 和发生的 event不再使用 select “参数-值”传递的方式。接口使用比 select 更方便。poll 并没有最大数量限制但是数量过大后性能也是会下降。2.2 无法逃避的性能瓶颈poll 的缺点虽然poll摆脱了位图也不用每次重置参数了但当并发量真正上来的时候它的致命缺陷就暴露了。每次调用 poll 都需要把大量的pollfd结构从用户态拷贝到内核中。这算是一个特点因为当你传的数据越多这次搬运的成本就越高这个开销是无法省去的。更严重的是poll 中监听的文件描述符数目增多时它和 select 函数一样poll 返回后需要轮询pollfd来获取就绪的描述符。同时连接的大量客户端在一时刻可能只有很少的处于就绪状态因此随着监视的描述符数量的增长其效率也会线性下降。这就是经典的O(n) 复杂度问题。假设你监听了 10 万个连接但这一刻只有 1 个连接有数据到达内核依然会遍历这 10 万个 fd白白浪费 CPU 算力。2.3 总结与过渡为什么需要 epollselect和poll共同的硬伤在于内核不保存你要监听的文件描述符集合每次调用都要重新传入而且内核是“盲找”的每次都要遍历所有 fd 才能知道谁就绪了。为了提升效率 —— 于是我们引入了epoll。它不仅能突破数据拷贝的瓶颈还能做到只返回就绪的 fd实现真正的 O(1) 事件通知好的本期内容就到这里如果对你有帮助还不要忘记点赞三联支持。我是此方我们下期再见。bye!