IO
例子:
假如有多台设备同时在充电,这个时候你要去拔掉完成充电的设备
BIO:(同步阻塞)假如先拔掉A,再拔掉B,这个时候C没有充电完,你就会在这里一直等待,等他充完再进行下一个。
NIO:(同步非阻塞)假设先拔掉A,再拔掉B,这个时候发现C没有充完,则会跳过这个C,继续拔掉下一个
比较典型实现NIO的两个:
select/poll:设备充完了会提示,但是你不知道谁提示了,要一个一个去问。
epoll:设备充完了会提示,你也知道了提示的是哪个设备,直接下去拔掉就好
同步阻塞(BIO)
单线程:某个socket阻塞了,会影响到其他socket处理
多线程:客户端较多时,会造成资源浪费,因为所有socket可能每个时刻也就只有几个就绪,同时,线程的调度,上下文切换也占有内存,可能会成为瓶颈
同步非阻塞(NIO)
优点:单个socket阻塞了,不会影响其他的socket
缺点:需要不断的进行系统调用,有一定的开销
select
FD就是文件描述符
将socket是否就绪检查逻辑下沉到操作系统层面,避免大量系统调用。
告诉你有事件就绪了,但是没告诉你具体是哪个FD
优点:不需要每个FD都进行系统调用,解决了频繁用户态内核切换问题。
缺点:
1、每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多的情况下是很大的。
2、不知道具体是哪个FD,需要遍历全部文件描述符
3、入参的3个fd_set集合每次调用都需要重置
4、单进程监听FD存在限制,默认1024
poll
FD就是文件描述符
跟select基础类似,优化了监听的1024限制
优点:不需要每个FD都进行系统调用,解决了频繁用户态内核切换问题。
缺点:
1、每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多的情况下是很大的。
2、不知道具体是哪个FD,需要遍历全部文件描述符3、入参的3个fd_set集合每次调用都需要重置4、单进程监听FD存在限制,默认1024
epoll
有几个函数
/**
创建一个epoll
size就是epoll要监听文件描述符的数量
**/
int epoll_create(int size)
/**
时间注册
epfd:epoll文件的描述符,epoll_create创建时返回
op :操作类型:新增(1),删除(2)、更新(3)
fd :本次要操作的文件描述符
epoll_event:需要监听的时间:读事件,写事件
如果调用成功返回0,不成功返回-1
**/
int epoll_ctl(int epfd,int op,int fd,int struct epoll_event *event);
/**
获取就绪事件
epfd: epoll的文件描述符,epoll_create创建时返回
event: 用户回传就绪事件
maxevents:每次能处理的最大事件数
timeout:等待I/O事件发生的超时时间,-1相当于阻塞,0相当于非阻塞
大于0:已就绪的文件描述符;等于0:超时,小于:出错
**/
int epoll_wait(int epfd,struct epoll_event *events , int maxevents , int timeout);
总结:高效处理高并发下的大量连接,同时有非常有益的性能
- 由于用户态和内核态共享epfd,避免了用户态–内核态切换的开销
- epoll_wait返回具有就绪文件描述符对象的个数,不再需要O(n)再遍历
缺点:
- 跨平台不好,只有linux支持
- 相较于epoll,select更轻量可移植性更强
- 在监听连接数和事件较少场景下,select可能更优