文章目录
博文所在专栏里有更多相关内容,如JAVA NIO、Reactor反应器模式等,欢迎阅读与交流。
文字来源于读书笔记及个人心得,可能有引用其他博文,若引用了你的文字请联系我,我会加上来源,或者删除相关内容。
一 IO概述
(一)碎碎念
- 程序进行IO基本都会用到底层(操作系统)的read&write,只是调用方法的名称可能不同
- read&write操作不直接与物理设备交互,而是与缓冲区交互——read是把数据从(操作系统)内核缓冲区复制到进程缓冲区,write是进程到内核
- 传统IO模型都是同步阻塞IO;
- Java默认创建的socket都是阻塞的;
- Java的NIO是New IO,属于IO多路复用;
- 操作系统底层用一个文件描述符(fd:file descriptor)来表示一个网络连接;
(二)常见的四种IO模型:
同步阻塞IO(Blocking IO):
由用户空间的线程主动发起IO请求(同步),需要内核IO操作彻底完成后才返回到用户空间执行用户的操作(阻塞)。
阻塞IO特点:一次调用发起后,用户程序空间阻塞,等待内核缓冲数据准备好并复制到用户缓冲区,复制完返回给用户程序空间,才解除阻塞
阻塞IO的优点:开发简单,阻塞期间用户线程被挂起基本不占用CPU资源
阻塞IO缺点:每个线程维护一个IO操作,并发情况下就需要大量线程维护大量网络连接,内存、线程切换开销巨大
同步非阻塞IO(Non-blocking IO):
由用户空间的线程主动发起IO请求(同步),用户空间的程序不需要等待内核IO操作彻底完成,在接收到内核立即返回给用户的状态值后,即可返回用户空间执行用户的操作。
同步非阻塞IO特点:用户程序线程需不停发起IO调用,直到内核缓冲区准备好数据,这时再发起一次IO调用时,然后用户程序空间阻塞,直到数据从内核缓冲区复制到用户缓冲区并返回给用户程序空间,才解除阻塞
优点:在内核缓冲区等待/准备数据时用户发起的IO调用都不会阻塞,实时性较好
缺点:不停的轮询会占用大量CPU时间,效率低下,因此高并发场景也不会直接使用NIO
IO多路复用(IO Multiplexing)
也称为异步阻塞IO,一个线程可同时监视多个文件描述符(一个文件句柄表示一个网络连接)。
在IO多路复用模型中通过select/epoll系统调用,单个应用程序的线程,可以不断地轮询成百上千的socket连接,当某个或者某些socket网络连接有IO就 绪的状态,就返回对应的可以执行的读写操作。
IO多路复用有三种实现方式:select、poll、epoll,其中poll、epoll是linux的函数,而Linux 2.5.44版本后poll被epoll取代。select和poll的时间复杂度都是O(N),select是数组有大小限制,poll是链表无大小限制;epoll的时间复杂度是O(1)
优点:一个选择器查询线程可同时处理大量连接,系统不必创建和维护大量线程
缺点:select,poll,epoll本质上都是同步阻塞I/O,因为他们都需要在读写事件就绪后自己(系统调用)本身进行读写,也就是说这个读写过程是阻塞的
IO多路复用的read流程:
①将目标socket注册到select/epoll选择器;
②使用选择器查询方法返回就绪的socket列表,用户进程在调用select方法的时候是线程阻塞的;
③对就绪socket发起read系统调用,用户线程阻塞,内核开始复制数据;
④复制完成,内核返回结果给用户线程,用户线程读取到数据,解除阻塞。
异步IO(Asynchronous IO)
指系统内核是主动发起IO请求的一方,用户空间的线程是被动接受方。
也称信号驱动IO。用户线程通过系统调用,向内核注册某个IO操作,内核在整个IO操作(数据准备、复制)完成后,发送signal给用户线程。
内核的数据处理过程中用户程序都不需要阻塞。
缺点:用户程序仅需要注册“接收内核IO操作完成的事件”或回调函数并接收该事件,其余工作都留给了操作系统,需要底层内核提供支持。
(三)小小总结
同步阻塞IO开发简单,阻塞时不占用CPU,但每个线程维护一个IO操作,并发情况下内存、线程切换开销巨大;
同步非阻塞IO在内核准备数据期间不阻塞,实时性较好,但不停的轮询会占用大量CPU时间,效率低下;
IO多路复用(异步阻塞)一个线程可操作多个IO,但本质上读写过程是阻塞的;
异步IO对Windows系统是可以的,但在linux下目前仍不完善,底层仍使用的是与IO多路复用系统的epoll。
因此,Linux下的高并发程序IO目前更推荐使用IO多路复用模型。
(四)文件描述符
文件描述符(FileDescriptor、FD):文件句柄,也叫文件描述符。文件描述符是内核为了高效管理已被打开的文件所创建的索引,它 是一个非负整数(通常是小整数),用于指代被打开的文件。所有的IO系统调用,包括socket的读写调用,都是通过文件描述符完成的。
linux默认文件句柄数为1024。
在Linux系统中, 文件可分为:普通文件、目录文件、链接文件 和设备文件。
linux获取单个进程能打开的最大文件句柄数量:ulimit -n
临时修改:ulimit -n 10000
永久修改:编辑/etc/rc.local,添加一句话:ulimit -SHn 10000,该方式不可大于硬极限值
终极修改:编辑/etc/security/limits.conf,添加:
soft nofile 100000
hard nofile 100000