IO 复用
IO 复用
单个线程通过记录跟踪每一个Sock(I/O流)的状态,来同时管理多个I/O流
用户输入的阻塞,导致如果服务端终止,那么它将看不到套接字的EOF,直到套接字读时为止,因此进程需要有预先告知内核的能力,使得内核能够发现进程指定的多个IO条件就绪时通知进程,这种能力被称为 IO复用
使用场景
- 客户处理多个描述符
- 处理多个套接字
- TCP服务器既要监听套接字,又要处理套接字
- 既要处理TCP,又要处理UDP
- 处理多个服务或协议
套接字输入操作
- 等待数据从网络到达(分组到达)
- 数据被复制到内核的某个缓冲区
- 把数据从内核缓冲区复制到应用进程缓冲区
UNIX I/O 模型
阻塞式 IO 模型
使用系统调用(例如 recvfrom)获取数据的全过程是阻塞的,直到成功获取数据或出现错误
非阻塞 IO 模型
poll 轮询
进程需要不断地调用 recvfrom 来查看是否有数据报已准备好接收,如果没有,会返回一个错误,缺点是会消耗大量 CPU 时间
IO复用模型 select
通过 select 阻塞调用返回套接字是否可读,可读后再调用 recvfrom ,这样就避免了轮询,但是系统调用从一个变成了两个,优势在于可以等待多个描述符就绪
信号驱动 IO 模型
让内核在描述符就绪时发送 SIGIO 信号,信号处理函数内调用 recvfrom
异步 IO 模型
调用非阻塞 aio_read 传入用户空间缓冲区指针和缓冲区大小,内核接收数据复制到用户空间的时候再递交给进程指定的信号,相当于回调函数
![][image-1]
epoll 事件驱动 IO 模型
epoll是一种当文件描述符的内核缓冲区非空的时候,发出可读信号进行通知,当写缓冲区不满的时候,发出可写信号通知的机制
- epoll内部使用了put_user将数据写入用户空间
- epoll基于事件驱动,epoll_ctl注册事件并注册callback回调函数,epoll_wait只返回发生的事件避免了像select和poll对事件的整个轮询操作,内核只要发现哪个套接字的数据来了,就会通知进程来取
epoll 的性能瓶颈(面试题): 如果是单机接收到的请求数量超过 50000,那么单机最大请求量就是瓶颈,需要进行负载均衡,如果没有超过,那么单机数据库 IO 请求处理能力将会成为瓶颈
内部实现:
- 事件回调队列
- fd红黑树
- 自旋锁保护队列,互斥量保护红黑树来保证线程安全
水平触发(level-trggered)
- 只要文件描述符关联的读内核缓冲区非空,有数据可以读取,就一直发出可读信号进行通知
- 当文件描述符关联的内核写缓冲区不满,有空间可以写入,就一直发出可写信号进行通知
边缘触发(edge-triggered)
- 当文件描述符关联的读内核缓冲区由空转化为非空的时候,则发出可读信号进行通知
- 当文件描述符关联的内核写缓冲区由满转化为不满的时候,则发出可写信号进行通知
区别
水平触发是只要读缓冲区有数据,就会一直触发可读信号,而边缘触发仅仅在空变为非空的时候通知一次