BIO(Blocking IO):阻塞IO(有的资料称同步阻塞IO),面向Stream。
NIO(Non-Blocking IO):非阻塞IO(有的资料称同步非阻塞IO),面向Channel。
多路复用IO(Multiplexing IO):也称事件驱动IO(event driven IO)或者Reactor模式(反应器模式)。
AIO(Asynchronous IO,也称NIO 2.0):异步IO(有的资料称异步非阻塞IO,也称Proactor模式(主动器模式)),面向Channel。
有的资料称同步才有阻塞、非阻塞之分,异步一定同时非阻塞,没有异步阻塞的操作。
NIO核心组件:Buffer(缓冲区)、Channel(通道)、Selector(选择器)。
NIO提供了BIO中Socket和ServerSocket对应的SocketChannel和ServerSocketChannel,这两种Channel都支持阻塞和非阻塞两种模式。
Selector在单个线程中处理多个Channel。
Selector中可以轮询ServerSocketChannel是否有连接请求,轮询SocketChannel是否有IO请求。
通过Channel将数据IO进Buffer,IO的同时可以进行其他操作。Channel是双向的,可读可写。Stream是单向的,读或者写。
NIO的IO之间是同步的,IO之间可以非阻塞地进行其他操作,所以NIO是异步非阻塞IO。
public class BIOServer {
public static void main(String[] args) throws IOException {
// TODO 服务端处理客户端连接请求
ServerSocket serverSocket = new ServerSocket(3333);
// 接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理
new Thread(() -> {
while (true) {
try {
// 阻塞方法获取新的连接
Socket socket = serverSocket.accept();
// 每一个新的连接都创建一个线程,负责读取数据
new Thread(() -> {
try {
int len;
byte[] data = new byte[1024];
InputStream inputStream = socket.getInputStream();
// 按字节流方式读取数据
while ((len = inputStream.read(data)) != -1) {
System.out.println(new String(data, 0, len));
}
} catch (IOException e) {
}
}).start();
} catch (IOException e) {
}
}
}).start();
}
}
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();
}
}
Netty是NIO的,Vert.x基于Netty。
Netty重写了JDK中NIO的Buffer、Channel。Windows上对AIO支持优秀,Linux的AIO底层和NIO一样是使用epoll,性能无显著优势,编码还更复杂,所以Netty使用的是NIO。
NIO从JDK1.4开始,AIO从JDK1.7开始。
IO分两个阶段,对于Input而言,先是数据进入操作系统内核(等待数据:wait for data),然后是将数据从操作系统内核拷贝到用户进程(拷贝数据:copy data from kernel to user)
内核:硬件上的第一层软件扩充,负责管理进程、内存、文件、网络、设备驱动。
阻塞IO和非阻塞IO的差异在于等待数据阶段是否阻塞。阻塞IO在等待数据阶段是阻塞的;读数据时,数据没有到达,会阻塞。非阻塞IO在等待数据阶段是非阻塞的;读数据时,数据没有到达,直接返回未到达标记,可以做其他的事情,下一次再来查看。非阻塞IO的读数据中,会轮询数据是否可读,如JDK的NIO:while(selectionKey.isReadable()){}
SelectionKey:
/**
* Tests whether this key's channel is ready for reading.
*
* <p> An invocation of this method of the form {@code k.isReadable()}
* behaves in exactly the same way as the expression
*
* <blockquote><pre>{@code
* k.readyOps() & OP_READ != 0
* }</pre></blockquote>
*
* <p> If this key's channel does not support read operations then this
* method always returns {@code false}. </p>
*
* @return {@code true} if, and only if,
{@code readyOps() & OP_READ} is nonzero
*
* @throws CancelledKeyException
* If this key has been cancelled
*/
public final boolean isReadable() {
return (readyOps() & OP_READ) != 0;
}
/**
* Tests whether this key's channel is ready for writing.
*
* <p> An invocation of this method of the form {@code k.isWritable()}
* behaves in exactly the same way as the expression
*
* <blockquote><pre>{@code
* k.readyOps() & OP_WRITE != 0
* }</pre></blockquote>
*
* <p> If this key's channel does not support write operations then this
* method always returns {@code false}. </p>
*
* @return {@code true} if, and only if,
* {@code readyOps() & OP_WRITE} is nonzero
*
* @throws CancelledKeyException
* If this key has been cancelled
*/
public final boolean isWritable() {
return (readyOps() & OP_WRITE) != 0;
}
/**
* Tests whether this key's channel has either finished, or failed to
* finish, its socket-connection operation.
*
* <p> An invocation of this method of the form {@code k.isConnectable()}
* behaves in exactly the same way as the expression
*
* <blockquote><pre>{@code
* k.readyOps() & OP_CONNECT != 0
* }</pre></blockquote>
*
* <p> If this key's channel does not support socket-connect operations
* then this method always returns {@code false}. </p>
*
* @return {@code true} if, and only if,
* {@code readyOps() & OP_CONNECT} is nonzero
*
* @throws CancelledKeyException
* If this key has been cancelled
*/
public final boolean isConnectable() {
return (readyOps() & OP_CONNECT) != 0;
}
/**
* Tests whether this key's channel is ready to accept a new socket
* connection.
*
* <p> An invocation of this method of the form {@code k.isAcceptable()}
* behaves in exactly the same way as the expression
*
* <blockquote><pre>{@code
* k.readyOps() & OP_ACCEPT != 0
* }</pre></blockquote>
*
* <p> If this key's channel does not support socket-accept operations then
* this method always returns {@code false}. </p>
*
* @return {@code true} if, and only if,
* {@code readyOps() & OP_ACCEPT} is nonzero
*
* @throws CancelledKeyException
* If this key has been cancelled
*/
public final boolean isAcceptable() {
return (readyOps() & OP_ACCEPT) != 0;
}
同步IO在拷贝数据阶段阻塞,所以IO只能顺序进行。异步IO发起IO请求后,不阻塞,直接返回,内核等待数据结束后,Proactor(前摄器,包含3个线程)将数据分发给用户进程,给用户进程一个信号,通知IO结束。
多路复用IO在等待数据阶段,可以在一个线程中通过select函数监控多个文件描述符的IO是否就绪,直接返回结果。监控时是阻塞的,可以设置监控时长,即阻塞时长;拷贝数据阶段阻塞,但可通过多线程分离IO,也有资料从这个角度称多路复用IO是异步阻塞IO,但这里的阻塞和阻塞IO的阻塞是不相同的,这里的异步和异步IO的异步是不相同的。
https://blog.csdn.net/m0_38109046/article/details/89449305
https://www.cnblogs.com/cainingning/p/9556642.html
https://www.cnblogs.com/straybirds/p/9479158.html
epoll是Linux特有。
select是POSIX(Portable Operating System Interface,可移植操作系统接口)的规定。
select、poll、epoll是同步的。
Linux的select、poll、epoll:
https://www.cnblogs.com/cainingning/p/9556642.html
https://segmentfault.com/a/1190000003063859
https://www.cnblogs.com/aspirant/p/9166944.html