网络相关 - BIO/NIO/AIO
- BIO 就是传统的 java.io包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者写入输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用时可靠的线性顺序。它的优点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。
- NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的对象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。
- AIO 是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非阻塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
基于BIO实现的伪异步I/O模型
采用单线程循环处理accept事件,当有客户端接入时,将客户端的socket封装成一个实现Runnable的Task放到后端的线程池中进行,线程池中维护一个消息队列N个活跃线程对队列中的任务进行异步处理。队列大小和线程数量可以固定大小,资源占用也就可控的,不用为每个客户端创建一个线程。
- 伪异步I/O的优点:
将accept事件和socke的读写事件进行了线程隔离,读写事件采用线程池进行了异步处理,不用为每个客户端创建一个单独的线程。 - 伪异步I/O的缺点:
socket的输入流和输出流的读写操作都是阻塞的,网络传输较慢时,读取输入流的线程会一直阻塞,如果线程池中的线程都处于读写阻塞中,其他消息只能在消息队列中排队,甚至将队列打满。所以无法从根本上解决同步I/O导致的通信线程阻塞问题。
基于I/O多路复用实现NIO
NIO 是利用了单线程轮询事件的机制,通过高效地遍历就绪的 Channel,来决定做什么,仅仅 select 阶段是阻塞的,可以有效避免大量客户端连接时,频繁线程切换带来的问题,应用的扩展能力有了非常大的提高。
- 首先,通过 Selector.open() 创建一个 Selector,作为类似调度员的角色;
- 然后,创建一个 ServerSocketChannel,并且向 Selector 注册,通过指定 SelectionKey.OP_ACCEPT,告诉调度员,它关注的是新的连接请求;
- 为什么我们要明确配置非阻塞模式呢?这是因为阻塞模式下,注册操作是不允许的,会抛出 IllegalBlockingModeException 异常;
- Selector 阻塞在 select 操作,当有 Channel 发生接入请求,就会被唤醒;
- 然后根据selectionKey与客户端建立链接,链接成功后,获取ServerSocketChannel通道,
并将该通道向Selector注册,开始关注通道可读状态; - 当有客户端写入数据,通道状态为可读状态时,selector线程会通知开始处理读取客户端数据;
- 主要通过缓冲区ByteBuffer来读取数据,读是非阻塞的。
- 读完客户端数据,开始写处理,写也是非阻塞的。
缓冲区 Buffer
在NIO类库中,所有数据都是通过缓冲区处理的,在读取数据时,直接读到缓冲区中,写入数据时,写到缓冲区中。任何时候访问NIO中的数据都是通过缓冲区进行操作。
缓冲区本质是数组,常用的实现类有
image.png
通道 Channel
Channel 与InputStream/OutputStream区别
Channel 是一个通道,可以用于读、写或者同时读写,是全双工的,可以双向流通,不阻塞;
Channel 主要有两类,分别用于网络读写的和文件读写操作;
InputSteam 是输入流,只能用于读数据,而且没有数据时会一直阻塞;
OutputStream 是输出流,只能用于写入输出数据;
多路复用器 Selector
Selector可以通过一个线程轮询注册在其上的Channel,如果某个channel上有了新的TCP连接接入、读和写事件,这个channel就处于就绪状态,会被selector轮询出来,然后通过selectionKey可以获取就绪channel的集合,进行后续的I/O操作。
一个Selector可以同时轮询多个Channel,在jdk5update10版本中进行了优化,使用epoll代替了原来的select/poll实现,没有了最大连接接句柄的限制。
NIO2.0 又叫AIO
NIO2.0引入了新的异步通道的概念,提供了异步文件通道和异步套接字通道的实现,异步通道提供两种方式获取操作结果。
- 通过java.util.concurrent.Feture类表示异步操作的结果。
- 在执行操作的时候传入一个java.nio.channels。
在异步I/O操作的时候可以传递信号变量,当操作完成之后会回调相关方法。它不需要通过多路复用器( Selector) 对注册的通道进行轮询操作即可实 现异步读写,从而简化了NIO 的编程模型。