在学习IO模型时看到好多文章晦涩难懂,如果不了解NIO的具体组件以及实际使用方式的话连IO模型都很难看懂。在此篇文章中不再详细解释NIO、BIO、AIO的内容,通过银行处理业务的例子来理解三种IO的模型。
假设现在有一个银行,一群客户。
传输层负责进程间的通信在WEB中HTTP底层使用TCP连接就以该协议解释。一个客户端TCP连接就是一个客户,一个银行是一个服务端进程。
BIO
一个人如果来办业务,工作人员就要等待客户讲需求,中间工作人员不干别的事,不处理其他人的业务。(阻塞)
多线程并发
现在银行中只有一个工作人员在窗口( 单线程+while(true) ),这个工作人员既负责对接客户又负责处理客户业务。如果这时多个客户来,也只能有一个客户被接待。
银行老板(系统背后的程序员)希望来了一批客户可以多客户同时处理,所以专门任命一个前台工作人员(处理连接的ServerSocket的请求,以 accept() 的方式在前台循环阻塞等待)负责接待客户。之后来一个客户前台就招一个临时工(创建实际处理Socket的线程),客户走了临时工也就走了(此时一个客户端连接对应一个服务端线程,通过多线程实现多客户同时连接)。
银行觉得招人辞退人(线程创建销毁)太麻烦了,所以又招了一批固定工作人员(线程池),即使没有客户的时候也养着这些人。
问题
到现在虽然实现了多客户同时处理业务的功能,但是效率却没有保证,比如一下这种情况:
如果有个客户他很忙,排到窗口坐在工作人员了又要打电话又要喝水上厕所,但工作人员此时也不能处理别的客户只能等着这个客户来说他的业务。(HTTP长连接中间并不是一直有IO发生)
NIO与IO复用
银行老板一看这可不行啊,都是这种客户银行还怎么挣钱。所以引入了新的方式让每一个客户来的时候下载一个APP,注册账户(账户即Selection Key)连接在后台,需要办理业务就在手机上发个消息给银行后台。
之前提到的前台给配台电脑能看这些消息,他会一直检查这些账户有没有业务需求(poll、select),如果发现有需求就去处理,没有就继续轮询。
本来一群人干的事现在一个人干。
一直检查电脑太累了,所以电脑设了个程序来业务了吱一声让前台知道来活了(epoll)并且直接显示出来哪个用户来业务了。
Selector、Channel、Buffer
前台就是Selector,连接就是channel,那buffer就是存在电脑上的客户发送来的业务请求。
连接不在一一对应线程。
AIO
此时还是有些问题,虽然说在正儿八经处理业务,但有的业务真的很长,比如有个客户要取出一百万纸币,光是机器吐钱就要花好久。
老板一看这不行啊,光等钱出来,其他的客户在等着也处理不了。
老板就找了个机器人,安排他去取送钱,取到钱了再通知前台。
这样来一个用户要取一个小目标(IO请求),前台看见了就直接发个命令让机器自己吐钱,等吐完了再让机器人把钱搬过来,前台直接把钱给客户(IO完成回调)。
零拷贝
再说说零拷贝,拷贝过程本来是
- 磁盘通过DMA到内核空间缓冲区
- cpu拷贝内核空间缓存到用户空间
- 用户空间再拷贝到内核空间目标文件缓冲区
- 内核空间最后通过DMA存到磁盘
那对应假想的银行来说就是转账(转账其实应该是剪切不是拷贝 哈哈,就当是没有扣钱的转账或者和剪切一样的拷贝)
- 拷贝就是机器吐钱出来
- 有车把钱送到前台
- 前台给告诉车该存到哪个账户
- 机器再把钱存进去
零拷贝实现两种方式分别为内存映射与文件传送。
内存映射
比普通拷贝少了一次系统调用,少了一次用户空间拷贝。
- 文件通过DMA拷贝到内核空间缓冲区(该缓冲区直接映射到用户空间)
- 内核空间通过cpu拷贝到目标文件缓冲区
- 再通过DMA拷贝到硬盘
银行
- 前台让机器把钱给吐出来给前台看了看(还和用户空间有联系)
- 不用把钱拿到前台了,直接让车送到要拷贝的账户
- 机器吃钱存回去
文件传送
应用直接把输入输出文件的文件描述符给系统调用,由操作系统自己拷贝到指定位置。
- DMA将文件从磁盘拷贝到内核缓存区
- cpu将文件从内核缓冲区拷贝到目标文件缓冲区
- 最后DMA存储
银行
前台通知机器把钱从A转到B
- 机器把A的钱吐出来(不用用户空间)
- 车自己送到到B账户
- 将钱再给吃回去到B账户