本文正在参加「Java主题月 – Java 刷题打卡」,详情查看活动链接
一、前言
面试官:说说下什么是 NIO
?
凡凡:emmmmm,NIO
?Java
的 NIO
还是 I/O
通信模型里?想坑劳资。。。
那先讲 I/O
吧,再引出 Java NIO
,恩,就这样。。。
聊聊就聊聊。
小算盘:
- 那先
I/O
通信模型:BIO
、NIO
、AIO
- 再 为什么要
Java NIO
? - 最后,
NIO
在网络上怎么用?select
、epoll
也给管上。
二、BIO
、NIO
、AIO
先来理解下:BIO
、NIO
、AIO
。
-
BIO
是 同步阻塞:每次操作卡在那,直到读写完成。但不是完全针对网络通信模型,同样适用磁盘文件的IO
读写(FileInputStream
) -
NIO
是 同步非阻塞:非阻塞,通过NIO
的FileChannel
发起个文件IO
操作,发起之后就返回了,可以去干别的事; 同步,还是得不断的去轮询去问操作系统,干完没。 -
AIO
是 异步非阻塞:非阻塞,通过AIO
发起个文件IO
操作后,立马可以返回干别的事了; 异步,操作系统自己干完IO
后,再过来通知。
(1)BIO
BIO
:同步阻塞式 IO
,是最传统的网络通信模型。
BIO
网络通信方式:
- 服务端创建一个
ServerSocket
- 客户端创建一个
Socket
去连接ServerSocket
ServerSocket
接收到一个Socket
连接请求,就创建一个Socket
和 一个线程 去跟 客户端Socket
通信。
客户端和服务端的
Socket
,进行同步阻塞式的通信:客户端Socket
发送一个请求,服务端Sokcet
进行处理后返回响应,响应必须是等处理完后才会返回。
如图:
当然可以搞一个线程池,如图:
线程池,使用固定线程数量来处理请求。
但是高并发请求的时候,没有更多线程来处理,还是可能会导致各种排队和延时。
(2)NIO
JDK 1.4
中引入了NIO
,这是一种同步非阻塞的IO
,基于Reactor
模型。
一些概念:
-
Buffer
缓冲区:一般将数据写入Buffer
中,然后从Buffer
中读取数据。 -
Channel
通道:NIO
中通过Channel
来进行数据读写。 -
Selector
多路复用器:Selector
会不断轮询注册的Channel
,如果某个Channel
上发生了读写事件,Selector
就会将这些Channel
获取出来,就可以进行IO
操作。一个
Selector
通过一个线程,就可以轮询成千上万的Channel
,这就意味着服务端可以接入成千上万的客户端。
如图:
(3)AIO
AIO
:是基于 Proactor
模型的,就是异步非阻塞模型。
读请求:
- 对应请求绑定
buffer
,通知操作系统去异步完成读取 - 期间,程序去干别的事
- 操作系统干完了,回调接口,通知你去从
buffer
中读取数据
写请求:
- 对应请求绑定
buffer
,通知操作系统去异步完成写数据 - 期间,程序去干别的事
- 操作系统干完了,回调接口,通知你写完成了
三、Java NIO
为什么需要 NIO
?
多数
Java
应用程序已不再受CPU
的束缚(把大量时间用在执行代码上), 而更改多时候是受I/O
的束缚 (等待数据传输)。
操作系统 与 Java
基于流的 I/O
模型有些不匹配:
即: 操作系统喜欢整卡车运来数据,
Java IO
类 则喜欢一铲子一铲子地加工数据
- 操作系统要移动的是大块数据 (缓冲区), 这往往是在硬件直接存储器存取 (
DMA
) 的协助下完成的。 JVM
的I/O
类喜欢操作小块数据 — 单个字节、几行文本。- 结果是: 操作系统送来整个缓冲区的数据,
Java IO
的流数据类再花大量时间把它们拆成小块, 往往拷贝一小块就要往返于几层对象。
然后呢,有了 NIO
, 就可以轻松地把一卡车数据备份到能直接使用的地方 (ByteBuffer
)
了解 Java NIO
,首先需要了解 三大重要组件:Buffer
、Channel
、Selector
-
Selector
:选择器用于监听多个通道的事件 (比如: 连接打开, 数据到达)因此, 单个线程可以监听多个数据通道。
-
Buffer
:可当作成缓冲区,或中转站 -
Channel
: 数据管道,用于在字节缓冲区和位于通道另一侧的实体 (通常是一个文件或套接字) 之间有效地传输数据基于通道(
Channel
) 和 缓冲区(Buffer
)进行操作, 数据总是从通道读取到缓冲区中, 或者从缓冲区写入到通道中
多路复用是啥?NIO
做网络编程又是怎样耍的?
计算机网络领域的
I/O
多路复用:
- 多路:指多条路
- 复用:指多条连接复用同一个阻塞对象
这个阻塞对象和具体的实现有关,在 Linux
下:
- 使用
select
:公共阻塞对象就是select
用到的fd_set
- 使用
epoll
:就是epoll_create
创建的文件描述符
I/O
多路复用技术归纳起来,有两个关键实现点:
- 当多条连接公用一个阻塞对象后,进程只需要一个阻塞对象上等待,而无须再轮询所有连接
- 当某条连接有新的数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始业务处理
那么了解完多路复用,来看下 NIO
做网络编程又是怎么耍的?
NIO
网络编程,流程如下:
-
创建
Selector
:这个就是阻塞对象 -
创建
ServerSocketChannel
:1.1 监听端口:例如 9000
1.2 只对接收请求感兴趣,并将其注册到
Selector
上:即告诉Selector
有接收请求就来通知我 -
Selector
查询就绪事件并执行 -
客户端请求服务建立连接:
3.1 与
ServerSocketChannel
建立连接3.2 并将对应的
SocketChannel
注册到Selector
上:可能对读请求或者写请求感兴趣3.3 客户端与
SocketChannel
通信
如图:
服务端,详细流程如下:
-
创建
Selector
-
创建
ServerSocketChannel
,监听端口,对接收请求感兴趣,并将其注册到Selector
上 -
Selector
查询就绪事件并执行-
若
key
为连接请求,注册读:即刚建立完连接(TCP
三次握手),那么就需要读取Header
等信息 -
若
key
为可读,注册写:即读取客户端信息后,需要服务端返回客户端信息例如:建立完连接后,服务端就需要读取请求信息
-
若
key
为可写,注册读:即服务端返回客户端信息后,等待客户端下次读请求例如:服务端读取完请求的信息后,想把一些数据告诉客户端
-
代码略。