UNIX中一共提出了五中I/O模型,分别是阻塞I/O、非阻塞I/O、多路复用I/O、信号驱动I/O、异步I/O。虽然关于这五种I/O模型的解释有很多,但是总感觉没有真正理解,如鲠在喉。也可能是因为自己的知识储备还不够,为了真正掌握I/O模型,需要从最开始的阻塞模型,一点点的记录模型的痛点及其发展的必然。从而达到融会贯通。
1. 阻塞型I/O
socket最开始采用的就是阻塞型I/O,这种模型比较好理解,当服务端需要监听网络文件描述符是否有数据准备好,会直接通过系统调用查看该文件符,如果数据并没有准备好,则会一直阻塞住,直到准备好为止。
此时的系统调用就是每次去查看一个文件描述符,查看是否有数据产生,有则返回,没有则阻塞。如果是主线程去做这个操作,那肯定会有很大的问题,进程将不会继续运行。此种情况时也是同步的,因为程序必须在读取到数据之后才会进行后续的操作。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
2.非阻塞型I/O
socket套接字,同样是每次去查看一个文件描述符,但是如果数据没有准备好,并不会阻塞在那里,而是会返回,这样的话,就可以让一个线程去轮询所有的文件描述符,当有的文件描述符有数据准备好时,就去处理。这样确实能够提高效率,但缺点也是很明显的。就是线程要去遍历所有的文件描述符,如果数量过多的话,也可能有些数据准备好了,但是还没轮询到它,处理起来也是比较消耗性能。这样也是同步,程序不断轮询,等待读取到数据然后进行后续操作。
3.多路复用I/O
首先明确一点,多路复用也是属于非阻塞I/O,但是为了解决非阻塞I/O每次去查看这个性能消耗,于是采取的优化措施
3.1select
最先出现的是select,为了避免一个个的去查看文件描述符,于是增加了select这种系统调用,每次可以调用一批文件描述符,然后由内核去查看有哪些文件描述符的数据准备好,然后再返回相应的文件描述符。但是由于系统调用采用的数组大小为1024,所以最大调用文件描述符个数为1024。
3.2 poll
总结:
同步阻塞、同步非阻塞、异步非阻塞。同步和异步是描述被调用者所能提供的状态,程序调用内核,内核只有只支持同步的处置数据时,那么程序无论是阻塞或是非阻塞,都是等有数据了,才能进行后续操作。但是如果内核支持异步,就是有个通知机制,那么程序利用异步调用后就可以继续后续操作,等数据准备好后就通知程序就行。其实这个可以类比程序调用消息队列:当程序采用同步方法发送消息到消息队列,必须阻塞的等消息队列返回成功才能进行后续操作。那么同步非阻塞呢?这种方法消息队列不提供,如果提供了大概就是这样,发送后会继续调用一个是否成功的接口,然后每次调用得到消息队列返回的信息都是’未保存好’,直到某次调用提示已’保存好’,那么程序就会停止调用该接口,进行后续的逻辑了,这就是同步非阻塞。而如果是异步了,那就像异步发送数据到消息队列一样,只需要用异步调用接口,然后实现回调方法,就不用管了,保存好后会通过回调方法通知程序,程序也从而知道是否保存好。。这里就是把消息队列类比为内核来进行理解。