IO模型
IO读写在用户缓冲区和内核缓冲区交互
1、阻塞IO
阻塞IO在两个阶段都会阻塞等待:数据就绪,数据拷贝(内核到用户)
执行流程
1、用户调用
recvfrom
发起系统调用请求内核2、等待数据就绪(没有准备好数据则会阻塞)
3、从内核拷贝数据到用户(没有完成则会阻塞等待)
4、拷贝完成发送标记给用户,处理数据
2、非阻塞IO
相较于阻塞IO,非阻塞IO的recvfrom
操作会立即返回结果而不是阻塞用户
如果数据没有准备就绪,那么不会阻塞用户而是返回block
标记,反复请求recvfrom
缺点
1、进程需要反复调用
recvfrom
,等待数据准备、拷贝完成后结束2、频繁的
recvfrom
相较于阻塞IO并没有提高效率,并且盲等机制会造成CPU空转
3、IO多路复用
无论是阻塞IO还是非阻塞IO,差别在于第一阶段准备数据就绪的处理不同;
比如服务器处理客户端Socket
请求时,在单线程情况下,一次只能处理一个Socket
,一旦正在处理的Socket
未就绪(不可读或者不可写)线程便会阻塞,这是所有的客户端Socket
都必须等待,性能自然会很差;
类比用户排队点餐,阻塞IO即第一个用户在思考要吃什么,后续的用户都会阻塞等待,而非阻塞IO的区别在于前台会一直询问你想好了吗
解决方案
1、增加更多的服务员(多线程,资源消耗大)
2、不排队,谁想好了吃什么(数据就绪),服务员就给谁点餐(处理数据)
用户进程如何知道资源准备就绪?
文件描述符简称
FD
是一个从0开始递增的无符号整数,用来关联Linux
中的每一个文件,在Linux
中一切皆文件,如常规文件,音频硬件设备,当然也包括Socket
网络套接字
多路复用是利用单个线程来同时监听多个
FD
,从而在某个FD
可读或者可写时得到通知,避免CPU进行无效的等待
执行
1、进程调用
select
同时监听多个Sockets
,数据准备就绪后返回readbale
可读标记2、进程反复调用
recvfrom
等待返回成功标志
监听FD的方式
三者的区别在于
select
和poll
只知道通知用户进程有FD就绪,而不知道具体是哪个FD
而epoll
则会通知用户进程FD
就绪的同时,把已经就绪的FD
写入用户空间
select
这里的FD存储单位是1比特,可以存储1024个比特位的FD
标识;
参数包括:
1、监听的
FD
集合(读写和异常集合)2、监听的最大fd+1
3、超时时间
流程
1、创建
fd_set
即1024比特的监听数组2、标记监听的
fd
(由低位到高位)3、执行
select
函数参数有读写异常数组、标记个数、最大fd
+14、将
fd_set
拷贝到内核空间进行监听,等待超时后清除未就绪的标记,将fd_set
拷贝回用户空间5、用户空间仍然不知道那些是标记的,需要遍历得到结果
存在的问题
1、需要在用户态拷贝到内核态后,再次拷贝回用户态,且用户态无法得知被标记的
fd
,需要遍历2、监听的数量只有1024
poll
性能提升相较于select仍然没有较大提升
IO流程
1、创建
pollfd
数组,向其添加关注的fd信息,数组大小自定义2、调用
poll
函数,将pollfd数组拷贝到内核态,链表存储无上限3、遍历fd,判断是否就绪
4、数据就绪或者超时后,拷贝
pollfd
数组到用户空间,返回就绪fd的数量n4、用户进程判断n是否大于0
5、大于0则遍历pollfd数组,找到就绪的
fd
存在的问题
相较于
select
监听,监听的数量提升了,但是遍历fd效率会降低
epoll
流程
1、
epoll_create
会在内核创建
eventpoll
结构体(存储fd集合的红黑树,存储就绪fd集合的链表)2、
epoll_ctl
(添加监听的fd,关联callback事件)callback触发,将就绪的fd添加到
redlist
3、
epoll_wait
检查redlist
不为空,返回个数并拷贝就绪的fd集合到events
中