网络 - I/O模型 nio
IO 复用
简介 :主要是select 和epoll 两个系统调用;对一个IO 端口,两次调用,两次返回,比阻塞IO 并没有什么优越性;关键是能实现同时对多个IO 端口进行监听;
I/O 复用模型会用到select、poll、epoll 函数,这几个函数也会使进程阻塞,但是和阻塞I/O 所不同的的,这两个函数可以同时阻塞多个I/O 操作。而且可以同时对多个读操作,多个写操作的I/O 函数进行检测,直到有数据可读或可写时,才真正调用I/O 操作函数。
select,poll,epoll 都是操作系统实现IO 多路复用的机制。我们知道,I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作
1、支持一个进程所能打开的最大连接数
select
单个进程所能打开的最大连接数有FD_SETSIZE 宏定义,其大小是32 个整数的大小(在32 位的机器上,大小就是3232,同理64 位机器上FD_SETSIZE 为3264),当然我们可以对进行修改,然后重新编译内核,但是性能可能会受到影响。
poll
poll 本质上和select 没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的
epoll
虽然连接数有上限,但是很大,1G 内存的机器上可以打开10 万左右的连接,2G 内存的机器可以打开20 万左右的连接
2、FD 剧增后带来的IO 效率问题
select
因为每次调用时都会对连接进行线性遍历,所以随着FD 的增加会造成遍历速度慢的“线性下降性能问题”。
poll
同上
epoll
因为epoll 内核中实现是根据每个fd 上的callback 函数来实现的,只有活跃的socket 才会主动调用callback,所以在活跃socket 较少的情况下,使用epoll 没有前面两者的线性下降的性能问题,但是所有socket 都很活跃的情况下,可能会有性能问题。
3、消息传递方式
select
内核需要将消息传递到用户空间,都需要内核拷贝动作
poll
同上
epoll
epoll 通过内核和用户空间共享一块内存来实现的。
总结:
综上,在选择select,poll,epoll 时要根据具体的使用场合以及这三种方式
的自身特点。
- 表面上看epoll 的性能最好,但是在连接数少并且连接都十分活跃的情况下,select 和poll 的性能可能比epoll 好,毕竟epoll 的通知机制需要很多函数回调。
- select 低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善
BIO
采用BIO 通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理没处理完成后,通过输出流返回应答给客户端,线程销毁。即典型的一请求一应答模型。
该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1 的正比关系,Java 中的线程也是比较宝贵的系统资源,线程数量快速膨胀后,系统的性能将急剧下降,随着访问量的继续增大,系统最终就死-掉-了。
为了改进这种一连接一线程的模型,我们可以使用线程池来管理这些线程,实现1 个或多个线程处理N个客户端的模型(但是底层还是使用的同步阻塞I/O),通常被称为“伪异步I/O 模型“。
使用FixedThreadPool 我们就有效的控制了线程的最大数量,保证了系统有限的资源的控制。
NIO
NIO 弥补了原来的I/O 的不足,它在标准Java 代码中提供了高速的、面向块的I/O。NIO 翻译成no-blocking io 或者new io 都说得通。
和BIO 的主要区别
面向流与面向缓冲 : Java NIO 和IO 之间第一个最大的区别是,IO 是面向流的,NIO 是面向缓冲区的。
阻塞与非阻塞IO: Java IO 的各种流是阻塞的。这意味着,当一个线程调用read() 或write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。
Java NIO 的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。
选择器(Selectors) : Java NIO 的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道
NIO 主要有三个核心部分组成: buffer 缓冲区、Channel 管道、Selector 选择器
Selector 中也会维护一个“已经注册的Channel”的容器。
通道,被建立的一个应用程序和操作系统交互事件、传递内容的渠道
Buffer
Buffer 用于和NIO 通道进行交互。数据是从通道读入缓冲区,从缓冲区写入到通道中的。
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存( 其实就是数组)。这块内存被包装成NIO Buffer 对象,并提供了一组方法,用来方便的访问该块内存。