不说废话
什么是IO多路复用:
单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力
有什么作用?(解决了什么问题):
CPU单核在同一时刻只能做一件事情,一种解决办法是对CPU进行时分复用,但线程进程创建,切换等比较浪费资源,在单线程/进程中处理多个事件流的方法:就是IO多路复用
IO多路复用解决的本质问题是在用更少的资源完成更多的事
I/O模型
目前Linux系统中提供了5种IO处理模型
我们这里不细讲阻塞IO和非阻塞IO,只讲IO多路复用
- 阻塞IO 串行化等待就绪(效率太低)
- 非阻塞IO 无限循环判断是否就绪(判断需要切换用户/内核态,浪费资源)
- IO多路复用 重要的三个方案(Linux系统下):select,poll,epoll
- 信号驱动IO
- 异步IO
提前需要知道的两个名词:Socket,FD
Socket:套接字。对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。例子:客户端将数据通过网线发送到服务端,客户端发送数据需要一个出口,服务端接收数据需要一个入口,这两个“口子”就是Socket。主要结构:接收队列(接收数据),等待队列(阻塞进程)
FD:文件描述符,非负整数。“一切皆文件”,linux中的一切资源都可以通过文件的方式访问和管理。客户端操作服务器时就会产生这三种文件描述符(简称fd):writefds(写)、readfds(读)、和 exceptfds(异常)。而FD就类似文件的索引(符号),指向某个资源,内核(kernel)利用FD来访问和管理资源。
Select
1.结构:
-
nfds:select中有nfds个FD被监听(默认1024)
- fd_set:底层使用位图表示,表达了两种意思;
- 入参时:监听的三种FD集合();回参时(内核空间遍历完成后):代表哪些FD就绪了
- timeval:超时的时间设置
2.流程:
1.假如如下图,我们服务器监控了四个Socket,拿到其对应的FD。
2.当在用户空间调用内核函数(IO操作)时,就会将FD拷贝一份到内核空间
3.之后就是遍历内核空间的FD,查看是否有该FD对应的Socket是否有数据到来(是否有就绪事件)
4.若有则在该FD打上标记,等待遍历完后返回就绪FD(只返回就绪个数,但可以通过fd_set来判断哪些FD已就绪)。拿到就绪FD,进行相应的IO操作
5.若没有,则将当前的调用用户线程给阻塞起来,等待如下场景:
客户端向服务器发送数据时,数据通过网络传输到服务器的网卡上,然后网卡会通过DMA的方式将这个数据包写入到指定的内存中。然后处理完成之后会通过中断信号告诉呃CPU有新的数据包到达。然后CPU收到中断信号后会进行响应中断,然后调用中断的处理程序进行处理。
(发送数据-->网卡-->内存-->中断处理数据)然后
首先根据这个数据包的IP跟端口号找到对应的这个socket
然后将这个数据保存到这个socket的一个接收队列
然后再检查这个socket对应的一个等待队列里面是否有进程正在阻塞等待
若有则唤醒该进程,从步骤4继续执行
Poll
1.结构:
2.流程:跟select基本一样,在select的结构上进行了优化
Epoll(重要)
主要三个函数:
-
epoll_create(创建一个epoll)
- epoll_ctl(绑定FD和事件,新增到或操作epoll)
- epoll_wait(获取就绪事件)
如图所示:
调用wait方法后:去查看就绪队列中有无,若无则当前进程阻塞,封装添加入等待队列中。
当客户端给服务器传递数据时(步骤如select中所描述)数据到socket,会调用当前socket,fd的回调函数,唤醒进程,然后添加fd到就绪队列,最后直接返回fd,不需要再用户空间再遍历一次。
select/poll/epoll 之间的区别:
基本区别:
使用区别:
100 万个连接,里面有 1 万个连接是活跃,可以对比 select、poll、epoll 的性能表现:
1️⃣select:不修改宏定义默认是 1024,则需要100w/1024=977个进程才可以支持 100 万连接,会使得 CPU 性能特别的差。
2️⃣poll:没有最大文件描述符限制,100 万个链接则需要 100 万个 fd,遍历都响应不过来了,还有空间的拷贝消耗大量的资源。
3️⃣epoll:请求进来时就创建 fd 并绑定一个 callback,只需要遍历 1 万个活跃连接的 callback 即可,既高效又不用内存拷贝。