介绍BIO 就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用时可靠的线性顺序。它的有点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。
NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。
AIO 是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
五种IO模型
Linux下的五种I/O模型
1)阻塞I/O(blocking I/O)
2)非阻塞I/O (nonblocking I/O)
3) I/O复用(select 和poll) (I/O multiplexing)
4)信号驱动I/O (signal driven I/O (SIGIO))
5)异步I/O (asynchronous I/O (the POSIX aio_functions))
前面四种都是同步io、第五种是异步IO;
阻塞(Blocking IO)
阻塞I/O 在调用一个io函数的时候,如果没有获取到数据的情况下,那么就会一直等待;等待的过程中会导致整个应用程序一直是一个阻塞的过程,无法去做其他的实现。
应用程序想要读取数据就会调用recvfrom,而recvfrom会通知OS来执行,OS就会判断数据报是否准备好(比如判断是否收到了一个完整的UDP报文,如果收到UDP报文不完整,那么就继续等待)。当数据包准备好了之后,OS就会将数据从内核空间拷贝到用户空间(因为我们的用户程序只能获取用户空间的内存,无法直接获取内核空间的内存)。拷贝完成之后socket.read()就会解除阻塞,并得到read的结果。
阻塞两个地方:1. OS等待数据报准备好。2.将数据从内核空间拷贝到用户空间。
BIO阻塞代码
public class ServerTcpSocket {
static byte[] bytes = new byte[1024];
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
try {
// 1.创建一个ServerSocket连接
final ServerSocket serverSocket = new ServerSocket();
// 2.绑定端口号
serverSocket.bind(new InetSocketAddress(8080));
// 3.当前线程放弃cpu资源等待获取数据
System.out.println("等待获取数据...");
while (true) {
final Socket socket = serverSocket.accept();
executorService.execute(new Runnable() {
public void run() {
try {
System.out.println("获取到数据...");
// 4.读取数据
int read = socket.getInputStream().read(bytes);
String result = new String(bytes);
System.out.println(result);
} catch (Exception e) {
}
}
});
}
} catch (Exception e) {
}
}
}
非阻塞(Non-Blocking IO)
非阻塞I/O 不管是否有获取到数据,都会立马获取结果,如果没有获取数据的话、那么就不间断的循环重试,但是我们整个应用程序不会实现阻塞。
NIO非阻塞式代码
public class ServerNioTcpSocket {
static ByteBuffer byteBuffer = ByteBuffer.allocate(512);
public static void main(String[] args) {
try {
// 1.创建一个ServerSocketChannel连接 final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 2.绑定端口号 serverSocketChannel.bind(new InetSocketAddress(8080));
// 设置为非阻塞式 serverSocketChannel.configureBlocking(false);
// 非阻塞式 SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
int j = socketChannel.read(byteBuffer);
if (j > 0) {
byte[] bytes = Arrays.copyOf(byteBuffer.array(), byteBuffer.limit());
System.out.println("获取到数据" + new String(bytes));
}
}
System.out.println("程序执行完毕..");
} catch (Exception e) {
e.printStackTrace();
}
}
}
NIO中提供了集中Channel:ServerSocketChannel;SocketChannel;FileChannel; DatagramChannel只有FileChannel无法设置成非阻塞模式,其他Channel都可以设置为非阻塞模式。
当我设置非阻塞后,我们的socket.read()方法就会立即得到一个返回结果(成功 or 失败),我们可以根据返回结果执行不同的逻辑,比如在失败时,我们可以做一些其他的事情。但事实上这种方式也是低效的,因为我们不得不使用轮询方法区一直问OS:“我的数据好了没啊”。
NIO 不会在recvfrom也就是socket.read()时候阻塞,但是还是会在将数据从内核空间拷贝到用户空间阻塞。一定要注意这个地方,Non-Blocking还是会阻塞的。
I/O复用(IO Multiplex)
IO实际指的就是网络的IO、多路也就是多个不同的tcp连接;复用也就是指使用同一个线程合并处理多个不同的IO操作,这样的话可以减少CPU资源。(单个线程可以同时处理多个不同的io操作,应用场景非常广泛:redis原理。Mysql连接原理)
操作系统提供一种机制(poll、select、epoll),允许注册IO请求,当有任何一个请求被触发,会有反馈
poll、select每次都要遍历所有的注册,并且轮询
epoll只会返回对应被触发的注册时间(并且提供了边缘触发,允许有条件的获取数据),并轮询
在windows操作系统中使用select实现轮训机制时间复杂度是为 o(n),而且这种情况也会存在空轮训的情况,效率非常低、其次默认对我们的轮训有一定限制,所以这样的话很难支持上万tcp连接。
所以在这时候linux操作就出现epoll实现事件驱动回调形式通知,不会存在空轮训的情况,只是对活跃的socket实现主动回调,这样的性能有很大的提升 所以时间复杂度为是o(1)
windows操作系统没有epoll、只有linux操作系统有。
为什么Nginx、redis能够支持非常高的并发 最终都是靠的linux版本的 io多路复用机制epoll
Redis的底层是采用nio 多路io复用机制实现对多个不同的连接(tcp)实现io的复用;能够非常好的支持高并发,同时能够先天性支持线程安全的问题。
信号驱动I/O(Signal driven IO)
发出一个请求实现观察监听,当有数据的时候直接走我们异步回调;
异步I/O(Asynchronous IO)
Asynchronous IO调用中是真正的无阻塞,其他IO model中多少会有点阻塞。程序发起read操作之后,立刻就可以开始去做其它的事。而在内核角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
NIO与BIO区别
传统的bio(同步阻塞 )是面向与流传输的,而NIO(同步非阻塞io)是面向与缓冲区非阻塞式的io,其中最大的亮点就是多路io复用机制。
NIO的核心组件
通道(Channel)
通常我们nio所有的操作都是通过通道开始的,所有的通道都会注册到统一个选择器(Selector)上实现管理,在通过选择器将数据统一写入到 buffer中。
缓冲区(Buffer)
Buffer本质上就是一块内存区,可以用来读取数据,也就先将数据写入到缓冲区中、在统一的写入到硬盘上。
选择器(Selector)
Selector可以称做为选择器,也可以把它叫做多路复用器,可以在单线程的情况下可以去维护多个Channel,也可以去维护多个连接;
同步和异步
同步方法表明调用一旦开始,调用者必须等待方法执行完成,才能继续执行后续方法。
异步方法表明,方法一旦开始,立即返回,调用者无需等待其中方法执行完成,就可以继续执行后续方法。
Bio演变的NIO的过程
Bio是一个阻塞式的io,不能够支持并发请求访问;可以多线程优化代码
这种方式也存在缺点:如果每个请求过来都使用一个线程,这时候非常浪费CPU的资源。
所以在网络编程服务器中,是否使用单线程提高响应的效率问题,所以nio出现;