IO模型与动车组
在很久很久以前有个高铁站,高铁站里有安检有站台。每当安检有旅客进入,才会调度动车进入站台,把站台上的旅客接走;要是安检没有旅客,就会一直等待,直到这趟车的旅客到达。一开始高铁站只有一条铁轨,所以每次只能等一辆动车全部接收完旅客才能运下一批。
后来人们觉得这种设计处理太慢了,于是在一条铁轨的基础上引入变轨。高铁站与高铁站之间,仍然还是一条轨道,但是进入高铁站开始变轨,一辆高铁一个轨道。这样就大大提高了运输效率。
到这里,就是我们生活中高铁站的运行。不过仍然没有跑出BIO的范围,只不过是应用了多线程。不考虑物理条件(牛顿的棺材板已经压死了),高铁站仍然可以进一步提高运输效率。 在实际生活中,安检一次只能等待一辆车次的旅客,提前20分钟检票,这样造成了动车阻塞式占据了站台资源。随后旅客到达站台,从站台进入动车,这部分仍然是阻塞的,但是不能进一步优化,因为我们不可能要求旅客一边跑一边上车。 所以,我们将安检进一步设计为:安检可以等待多辆不同车次的旅客,当有旅客到达时,通知对应列车来站台接客。到这里就是所谓的NIO模型了。
现在用计算机语言重新描述一下IO模型。一开始客户端和服务端建立TCP连接,三次握手后服务端accept连接,随后阻塞线程读取客户端数据,也就是等待旅客从安检到站台的过程。再细分阻塞读取步骤:从网卡到内核缓冲区,从内核缓冲区到用户缓冲区。其中到达内核缓冲区后,会修改文件状态并告知线程读就绪。这部分就相当于高铁站中,到达安检(内核缓冲区),调度动车进站(读就绪),旅客到达站台(用户缓冲区)
服务器基于BIO的单线程模式,主线程将阻塞在等待读取请求数据,不能处理其他请求。也就是我们一开始说的,动车一直等待旅客,由于铁轨(线程)只有一条,其他车次不能进站。
仍然是BIO但是应用多线程后,每个请求都会为其新建一个线程处理,不过仍然阻塞式等待请求数据。对应后来我们提到的变轨高铁站。
但是BIO终究是有极限的,一个TCP长连接上,read大部分时间阻塞在了HTTP请求网络传输上,所以对read方法进一步改进,没有数据到达就不等了,于是NIO就出来了。程序记录所有accept连接,轮询每个文件的状态,有数据到达进一步唤起线程处理。
进一步再深入NIO就发现这个轮询有丶东西。
首先我们普及一下什么用户态和内核态。
现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。 操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。 针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。
然后时文件描述符(fd)
是一个用于表述指向文件的引用的抽象化概念。 fd是一个非负整数。实际上,它是一个索引值,通过fd操作系统可以快速找到对应文件。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着fd展开。但是fd这一概念往往只适用于UNIX、Linux这样的操作系统。
作者:一角钱技术 链接:juejin.cn/post/688298… 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
操作系统提供了select命令,将一个文件描述符的数组发给操作系统,如果有读就绪的文件则返回数量。所以这个命令实现起来就很难受了,因为返回不告诉你哪个文件可读,而是告诉你数组里有几个文件可读,至于哪几个就需要你自己去遍历。所以基于select实现轮询时间复杂度就,一旦数据量提升,效率有很明显的下降。并且文件描述符数组发给操作系统时,还涉及用户态和内核态的copy,同样随数据量提升复制的开销增大。最后,单个线程传递文件描述符的数量时有极限的,最多1024。
操纵系统还提供了一个命令:poll。不过poll和select本质上没啥区别,只不过poll没有fd传输的数量限制。
后来linux提供了epoll命令用于高效轮询。不过在实现上,epoll实际和轮询没啥关系,因为epoll是事件驱动+回调方式实现的。当有数据到达,文件处于可读状态,通过回调函数,epoll会将这个IO事件传递给线程,不再需要对所有fd进行轮询,从而提高效率。
参考文献:
[1]: 图解 | 原来这就是 IO 多路复用
[2]: Understanding the Tomcat NIO Connector and How to Configure It