chengwei0 2019-08-22
在 Linux IO 编程中,如果需要同时处理多个客户端请求,可以利用 IO多路复用技术,把多个 IO阻塞 复用到同一个 select(系统调用)阻塞上。这样就能达到 单个线程同时处理多个客户端请求 的效果。
与传统的多线程方式相比,IO多路复用 可以减少系统开销。因为所需线程少,节省了很多系统资源。
Linux 内核将所有外部设备都当作一个文件来操作;
而Linux中对一个文件的读写操作会调用系统命令,返回一个文件描述符 FD。
对一个 socket 的读写也有相应的描述符 —— socketfd。
这些描述符就是一个数字,它指向内核中的一个结构体。结构体中包含 文件路径、数据区 等属性。
利用 Linux 中的 select/poll 机制,可以将多个文件描述符 FD 传给 select 或 poll 系统调用;
select/poll 可以侦测这些 FD 是否处于就绪状态,以便处理相应的IO操作。
select/poll 以 顺序扫描 的方式检测FD的就绪状态,且支持的 FD 数量有限,所以实际应用中容易成为瓶颈。
为了克服select的缺陷,出现了多种解决方案(如,FreeBSD 的 kqueue、Solaris 的 dev/poll)。
epoll 系统调用就是其中一种方案。与 select 相比,它有很多大改进:
select 支持单进程打开的 FD数量较小,默认值是 1024。
对于动辄上万个客户端连接的服务端来说根本不够用。
epoll 支持的 FD数量 上限是 操作系统的最大文件句柄数,远大于1024。
这个数字通常与内存关系较大。一般,内存越大,这个上限就越高。
1GB内存的机器支持大约10万个句柄。
可以通过以下命令查看该数值:
cat /proc/sys/fs/file-max
现实应用中经常会有这种场景:客户端连接非常多(即 socket集合 非常大),且网络延时或链路空闲导致“活跃”socket非常少。
传统 select/poll 是顺序扫描 socket 集合,检查就绪状态。
这导致发现“活跃”socket的效率 因FD数量增加而线性下降。
epoll 则只会对“活跃”的socket 进行操作。
因为它是根据每个 FD 上的 callback 函数实现的。
只有“活跃”的socket 才会主动调用 callback 函数。
也就是“事件驱动”,这是epoll名称的由来(event poll)。
可以把 epoll 的这个机制当作是 “伪异步IO”。
显然,如果所有 socket 都处于活跃状态,那么 epoll 在这方面没有效率优势,甚至不如传统select。
但在现实场景中,经常会出现很多 socket 处于非活跃状态,epoll 的效率优势非常明显。
为了把内核中的FD相关数据通知到用户空间,epoll 利用 mmap 让内核与用户空间 映射同一块内存。这样就可以避免不必要的内存复制。