一、io读写的基础原理
用户程序的IO读写程序,在大多数情况下,并没有进行实际的IO操作,而是在进程缓冲区和内核缓冲区之间直接进行数据的交换。
- 用户程序进行IO的读写,依赖与底层的IO,通过调用底层的read&write两大系统。
- read&write系统数据写入读取都是与缓冲区进行数据交换,操作系统中有许多的缓冲区,多是为了减少与设备之间的物理交换。外部设备的读写都涉及到操作系统的中断,发生中断时需要保存进程数据的状态信息,所以需要减少与物理设备的直接交换。
- 缓冲区的数据在达到一定的数量的时候,底层操作系统会进行IO设备的终端处理,集中执行物理设备的实际IO操作。
- linux系统中,操作系统内核只有一个内存缓冲区,每个用户程序(基进程)有自己独立的进程缓冲区。
重复一下结论:用户程序的IO操作,大多数情况下,都是缓冲区之间的数据交换,并不是实际的物理设备交换数据。
二、4种主要的IO模型
同步阻塞IO(BIO)
同步阻塞,client和server之间线程是1对1的关系,server线程会等待client线程写入数据,如果client不写入线程,那server线程会一直阻塞。
建立一个一对一操作,client的操作和server的操作是相互依赖的,某一个阻塞了,另一个也会阻塞等待。
同步非阻塞IO(NIO)
同步:1对1
非阻塞:client发起io请求,如果server没有准备好数据,server会直接返回错误。client会一直轮询去访问server是否准备好了数据。
- 在内核缓冲区没有数据的情况下,系统调用会立即返回,返回一个调用失败的信息。
- 在内核缓冲区中有数据的情况下,是阻塞的,阻塞直到数据从内核缓冲区复制到进程缓冲区。复制完成后,系统调用返回成功,应用程序开始处理用户空间的缓存数据。
IO多路复用(重点)
典型场景:Java NIO(new IO)、redis线程模型
下一篇文章是对Java NIO的理解Java NIO的理解
select、epoll
- server使用一个线程去处理客户端的请求,server和client的关系是1:n。
- server端复用一个线程处理多个client的请求。
异步IO(AIO)
- 客户端发起请求之后就做自己的事情去了,客户端会注册一个回调函数,server数据准备好之后去调用client注册的回调函数。
- 异步IO在windows系统下通过IOCP实现了真正的异步IO,但是在linux下,异步IO模型在2.6版本才引入,目前并不完善,底层使用的任然是epoll,与IO多路复用相同,所以在性能上面没有明显的优势。
- 性能上没有明显的优势,反而编程更为复杂,所以目前高并发网络应用程序的开发,大多是采用IO多路复用。
三、epoll、select、poll函数
操作系统的底层函数
poll:遍历链表,每次遍历都会线性遍历,时间复杂度为O(n),底层实现是链表,长度无限制。
select:遍历数组,每次遍历都会线性遍历,时间复杂度为O(n),底层实现是数组,长度有限制。
epoll:回调方式调用,底层实现是哈希表,使用事件通知方式,每当有IO事件就绪,系统注册的回调函数就会被调用,事件复杂度为O(1),长度是无限制的。
poll->select->epoll 主动轮询到被动回调。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。