首先在分析他们之前的区别前,需要明确几个概念。
同步和异步的概念:
同步是指用户线程发起IO请求后,需要等待或者轮询内核IO操作完成后才能继续执行;
异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程或者调用用户线程注册的回调函数。
阻塞和非阻塞的概念:
阻塞是指IO操作需要彻底完成后才返回到用户空间;
非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。
I/O 同步和异步的区别在于:将数据从内核复制到用户空间时,用户进程是否会阻塞。
I/O 阻塞和非阻塞的区别在于:进程发起系统调用后,是会被挂起直到收到数据后在返回、还是立即返回成功或错误。
一般来讲一个IO分为两个阶段:
现在假设一个进程/线程A,试图进行一次IO操作。
A发出IO请求,两种情况:
第一种情况就是非阻塞,A为了知道数据是否准备好,需要不停的询问,而在轮询的空歇期,理论上是可以干点别的活。
第二种情况就是阻塞,A除了等待就不能做任何事情。
数据终于准备好了,A现在要把数据取回去,有几种做法:
第一种情况,所有的事情都是同一个线程做,叫做同步,有同步阻塞(BIO)、同步非阻塞(NIO)
第二种情况,叫做异步,只有异步非阻塞(AIO)
文件描述符:系统为每一个进程维护了一个文件描述符表,表示该进程打开文件的记录表,而文件描述符实际上就是这张表的索引(下文代称fd)。
多路复用:为了实现一个服务器可以支持多个客户端连接。
socket 创建连接过程,并把tcp/udp包含地址、类型和通信协议等信息对底层的封装进行屏蔽,返回一个连接或者fd。socket 通信,实际上就是通过文件描述符 fd 读写文件,这也符合 Unix“一切皆文件”的哲学。
select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符fd,一旦某个描述符就绪(一般是读就绪或者写就绪),系统会通知有I/O事件发生了(不能定位是哪一个)。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
select、poll和epoll这三个函数是Linux系统中I/O复用的系统调用函数。I/O复用使得这三个函数可以同时监听多个文件描述符(File Descriptor, FD),因为每个文件描述符相当于一个需要 I/O的“文件”,在socket中共用一个端口。
工作原理
Select可以是非阻塞模型,非阻塞并不一定是异步模型,但异步模型一定是非阻塞的。利用 select 函数来判断某Socket上是否有数据可读,或者能否向一个套接字写入数据,防止程序在Socket处于阻塞模式中时,在一次 I/O 调用(如send或recv、accept等)过程中,被迫进入“锁定”状态;
优点:
缺点:
可以认为poll是一个增强版本的select,因为select的比特位操作决定了一次性最多处理的读或者写事件(文件描述符数量)只有1024个,而poll使用一个新的方式优化了这个模型。
poll底层操作的数据结构pollfd:
struct pollfd {
int fd; // 需要监视的文件描述符
short events; // 需要内核监视的事件
short revents; // 实际发生的事件
};
优点:
缺点:
epoll给出了一个新的模式,直接申请一个epollfd的文件,对这些进行统一的管理,初步具有了面向对象的思维模式。可理解为event poll,epoll会把哪个流发生哪种I/O事件通知我们。所以epoll是事件驱动(每个事件关联fd)的,此时我们对这些流的操作都是有意义的。复杂度也降低到了O(1)。
epoll通过在Linux内核中申请一个简易的文件系统。把原先的select/poll调用分成了3个部分:
如此一来只需要在进程启动时建立一个epoll对象,然后在需要的时候向这个epoll对象中添加或者删除连接。同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复制这些连接的句柄数据,内核也不需要去遍历全部的连接。
1、支持一个进程所能打开的最大连接数
select:单个进程所能打开的最大连接数有FD_SETSIZE宏定义,其大小是32个整数的大小(在32位的机器上,大小就是3232,同理64位机器上FD_SETSIZE为3264),当然我们可以对进行修改,然后重新编译内核,但是性能可能会受到影响,这需要进一步的测试。
poll:poll本质上和select没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的。
epoll:虽然连接数有上限,但是很大,1G内存的机器上可以打开10万左右的连接,2G内存的机器可以打开20万左右的连接。
2、fd剧增后带来的IO效率问题
select:因为每次调用时都会对连接进行线性遍历,所以随着FD的增加会造成遍历速度慢的“线性下降性能问题”。
poll:同上
epoll:因为epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的线性下降的性能问题,但是所有socket都很活跃的情况下,可能会有性能问题。
3、 消息传递方式
select:内核需要将消息传递到用户空间,都需要内核拷贝动作
poll:同上
epoll:epoll通过内核和用户空间共享一块内存来实现的。