文章目录
- 一.IO模型与同步/异步、阻塞/非阻塞的关系
- 1.什么是IO模型
- 2.同步与异步【侧重于IO进程能不能立即响应信号】
- 3.阻塞与非阻塞【侧重于调用者是否需要等待IO进程】
- 4.==三种IO模型==
- 二.Java的BIO、NIO和AIO
- 1.BIO【Blocking I/O】- 同步阻塞IO
- 客户端-服务器 链路一一对应模式
- 代码实现
- 2.NIO【New I/O 或 Non-Blocking I/O】- 同步非阻塞IO
- 1.Buffer
- 2.Channel
- 3.Selector
- 4.NIO整体架构图
- 5.代码实现
- 3.AIO【Asynchronous I/O】- 异步非阻塞IO
一.IO模型与同步/异步、阻塞/非阻塞的关系
1.什么是IO模型
IO模型是操作系统对输入和输出做的一系列规范和实现。
2.同步与异步【侧重于IO进程能不能立即响应信号】
- 同步:同步指的是调用者发起IO调用后,IO进程在处理完成之前,都不返回信号。
- 异步:异步指的是调用者发起IO调用后,IO进程立即返回接受信号,此时调用者和IO进程都在并发执行各自的任务。当IO进程完成任务后,通过回调机制通知调用者。
3.阻塞与非阻塞【侧重于调用者是否需要等待IO进程】
- 阻塞:调用者发起IO调用之后,需要等待IO进程完成任务,需要挂起当前进程。
- 非阻塞:调用者发起IO调用之后,调用者可以继续执行其他任务而不需要挂起。
4.三种IO模型
三种IO模型即为BIO【同步阻塞IO】、NIO【同步非阻塞IO】、AIO【异步非阻塞IO】。
首先可以先简单地体会一下三种IO模型的情景:假设你需要等待开水的煮开:
- 同步阻塞:你蹲在开水壶旁边,什么都不做,等着开水的煮开。
- 同步非阻塞:你跑去看书学习,隔一段时间来看看开水是否煮开。
- 异步非阻塞:你跑去看书学习,当开水煮开的时候会发出响声,听到响声后你再回来。
二.Java的BIO、NIO和AIO
1.BIO【Blocking I/O】- 同步阻塞IO
在BIO模式下,数据的写入和读取都必须阻塞在一个线程中执行,在写入完成或读取完成前,线程阻塞。
客户端-服务器 链路一一对应模式
在传统的BIO中,一个客户端请求服务器后,服务器会经过Sokcet启动一条链路将其连接并且处理,该链路的IO操作的同步阻塞的,所以该客户端和服务器的连接不可被其他客户端所使用,只能够等待当前的客户端操作完成后释放掉当前连接。
代码实现
- BIOClient
package io_learn.BIO;
import java.io.IOException;
import java.net.Socket;
import java.util.Date;
import java.util.Scanner;
/**
* @Auther: ARong
* @Date: 2019/11/29 5:42 下午
* @Description: BIO 的 客户端
*/
public class BIOClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 6666);
Scanner input = new Scanner(System.in);
while (input.hasNext()) {
socket.getOutputStream().write(input.nextLine().getBytes());
}
}
}
- BIOServer
package io_learn.BIO;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Auther: ARong
* @Date: 2019/11/29 5:38 下午
* @Description: BIO 的 服务端
*/
public class BIOServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(6666);
while (true) {
// 阻塞直到有客户端连接
Socket socket = serverSocket.accept();
// 处理请求
int len = -1;
byte[] data = new byte[1024];//每次读取1k
InputStream inputStream = socket.getInputStream();
while ((len = inputStream.read(data)) != -1) {
System.out.println(new String(data, 0, len));
}
}
}
}
2.NIO【New I/O 或 Non-Blocking I/O】- 同步非阻塞IO
NIO相对于BIO来说出现了几个核心的组件,分别是Selector(选择器)、Channle(通道)和Buffer(缓冲区)。NIO出现于JDK 1.4之后。
1.Buffer
缓冲区的出现导致了NIO和BIO的不同:读数据时可以先读一部分到缓冲区中,然后处理其他事情;写数据时可以先写一部分到缓冲区中,然后处理其他事情。读和写操作可以不再持续,所以不会阻塞。当缓冲区满后才会将其放入真正地读/写。
2.Channel
NIO的所有IO操作都从Channle开始:
- 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。
- 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。
3.Selector
选择器可以让单个线程处理多个通道,达到复用的目的:
4.NIO整体架构图
5.代码实现
- NIOServer
/**
*
* @author 闪电侠
* @date 2019年2月21日
* @Description: NIO 改造后的服务端
*/
public class NIOServer {
public static void main(String[] args) throws IOException {
// 1. serverSelector负责轮询是否有新的连接,服务端监测到新的连接之后,不再创建一个新的线程,
// 而是直接将新连接绑定到clientSelector上,这样就不用 IO 模型中 1w 个 while 循环在死等
Selector serverSelector = Selector.open();
// 2. clientSelector负责轮询连接是否有数据可读
Selector clientSelector = Selector.open();
new Thread(() -> {
try {
// 对应IO编程中服务端启动
ServerSocketChannel listenerChannel = ServerSocketChannel.open();
listenerChannel.socket().bind(new InetSocketAddress(3333));
listenerChannel.configureBlocking(false);
listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);
while (true) {
// 监测是否有新的连接,这里的1指的是阻塞的时间为 1ms
if (serverSelector.select(1) > 0) {
Set<SelectionKey> set = serverSelector.selectedKeys();
Iterator<SelectionKey> keyIterator = set.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
try {
// (1) 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector
SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
clientChannel.configureBlocking(false);
clientChannel.register(clientSelector, SelectionKey.OP_READ);
} finally {
keyIterator.remove();
}
}
}
}
}
} catch (IOException ignored) {
}
}).start();
new Thread(() -> {
try {
while (true) {
// (2) 批量轮询是否有哪些连接有数据可读,这里的1指的是阻塞的时间为 1ms
if (clientSelector.select(1) > 0) {
Set<SelectionKey> set = clientSelector.selectedKeys();
Iterator<SelectionKey> keyIterator = set.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isReadable()) {
try {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// (3) 面向 Buffer
clientChannel.read(byteBuffer);
byteBuffer.flip();
System.out.println(
Charset.defaultCharset().newDecoder().decode(byteBuffer).toString());
} finally {
keyIterator.remove();
key.interestOps(SelectionKey.OP_READ);
}
}
}
}
}
} catch (IOException ignored) {
}
}).start();
}
}
从以上代码实例可以看出,使用原生的Buffer、Channel和Selector来实现NIO还是很麻烦的,所以才会出现Netty,Netty能够让我们快速便捷地实现NIO。还有一个原因是,JDK原生的NIO是基于操作系统的epoll函数的,而这个函数可能会引发阻塞,而Netty是给予select函数的。
3.AIO【Asynchronous I/O】- 异步非阻塞IO
AIO出现于JDK 1.7,是NIO的改进版,主要基于事件和回调机制来实现异步处理。