一,BIO- 同步阻塞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));
}
}
}
}
二,NIO- 同步非阻塞IO
NIO相对于BIO来说出现了几个核心的组件,分别是 Selector(选择器) 、 Channle(通道) 和 Buffer(缓冲区) 。NIO出现于JDK 1.4之后。
1.Buffer
缓冲区的出现导致了NIO和BIO的不同:读数据时可以先读一部分到缓冲区中,然后处理其他事情;写数据时可以先写一部分到缓冲区中,然后处理其他事情。读和写操作可以不再持续,所以不会阻塞。当缓冲区满后才会将其放入真正地读/写。
2.Channel
NIO的所有IO操作都从Channle开始: - 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。 - 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。
3.Selector
选择器可以让单个线程处理多个通道,达到复用的目的:
4.NIO整体架构图
NIOServer
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函数的。
三,AIO- 异步非阻塞IO
AIO出现于JDK 1.7,是NIO的改进版,主要基于事件和回调机制来实现异步处理。
ava AIO 采用订阅-通知
模式:即应用程序向操作系统注册IO监听,然后继续做自己的事情。当操作系统发生IO事件,并且准备好数据后,在主动通知应用程序,触发相应的函数。
Java AIO 的处理流程图如下:
- 和同步IO一样,异步IO也是由操作系统进行支持的。微软的windows系统提供了一种异步IO技术:
IOCP(I/O CompletionPort,I/O完成端口)
; - Linux下由于没有这种异步IO技术,所以使用的是
epoll
(上文介绍过的一种多路复用IO技术的实现)对异步IO进行模拟。
四,IO技术比较
BIO/NIO/AIO比较
BIO | NIO | AIO | |
---|---|---|---|
IO模型 | 同步阻塞 | 同步非阻塞(多路复用) | 异步非阻塞 |
编程难度 | 简单 | 复杂 | 复杂 |
可靠性 | 差 | 好 | 好 |
吞吐量 | 低 | 高 | 高 |
BIO、NIO、AIO适用场景
-
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
-
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
-
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。