bio/nio/aio
先看一下这三种的名词解释。
bio:同步阻塞io;
nio:同步非阻塞io;
aio:异步非阻塞io;
通过解释之后,引申出几个概念,什么是同步与异步,什么是阻塞与非阻塞。
同步就是需要自己去监听事件是佛欧有返回结果,而异步是其他线程去做这件事。
阻塞的意思的,比如在bio中,我调用了accept方法,会一直阻塞在这里,直到有连接事件发生,这种酒属于阻塞事件,非阻塞就是在nio中我们调用accept方法,会马上返回结果,线程可以去处理其他事情,这就是非阻塞。
bio模型:
nio模型:
那么为什么netty不使用看上去更高大上的aio,而是使用nio的,其实netty5是使用aio的,但是因为操作系统对aio的支持还不是特别好,而且aio的代码处理逻辑又特别复杂,所以netty5官方不建议使用,现在主流的版本是netty4还是在使用nio的。
多路复用器
当使用bio我们服用断的代码是这样的。
package cn.darwin.base.bio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class BioServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9001);
for (; ; ) {
final Socket accept = serverSocket.accept();
new Thread(() -> {
try {
while (true) {
byte[] bytes = new byte[1024];
final int read = accept.getInputStream().read(bytes);
if (read != -1) {
System.out.println(new String(bytes));
}
accept.getOutputStream().write("欢迎".getBytes(StandardCharsets.UTF_8));
accept.getOutputStream().flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
在调用accept的时候会堵塞住,做不了其他的操作,这也就是为什么bio会有著名的c10k问题,就是一个服务端连接1W个客户端,性能就会有极大的瓶颈。
而nio将channel设置为非阻塞之后,accept将会立刻返回结果,我们可以将这些个channel放到一个集合中去,然后循环遍历这个list有没有事件发生,即可一个线程处理多个客户端的连接,代码如下:
package cn.darwin.base.nio;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ServerChannel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class NioServer {
public static void main(String[] args) throws IOException {
List<SocketChannel> channelList = new LinkedList<>();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9002));
final Selector open = Selector.open();
serverSocketChannel.configureBlocking(false);
while (true) {
final SocketChannel accept = serverSocketChannel.accept();
if (accept != null) {
accept.configureBlocking(false);
channelList.add(accept);
}
final Iterator<SocketChannel> iterator = channelList.iterator();
while (iterator.hasNext()) {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
final int read = iterator.next().read(byteBuffer);
if (read != -1) {
System.out.println(new String(byteBuffer.array()));
}
}
}
}
}
那么,这样就是比较好的操作了么,假设我们现在一共有100000个链接,每次只有10个左右的连接会有事件发生,我每次要去遍历所有的list么?多路复用器就是我们只去获取那些有事件发生的文件描述符。这种在操作系统层面有几种实现,poll、select、epoll,poll和select还是轮训每个文件操作符看哪个有事件发生,而epoll是在内部维护了一个红黑树和链表,当有数据到达网卡的时候,会触发硬中断,会把这些有事件的放到红黑树中,然后当调用系统函数epoll_wait的时候会将有事件的附件描述符返回。epoll还有两个系统调用,分别是epoll_create和epoll_ctl,分别用于初始化epoll和把文件描述符添加到红黑树中。这就是nio的核心组件selector主要做的事情。
package cn.darwin.base.nio;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ServerChannel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class NioServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9002));
serverSocketChannel.configureBlocking(false);
final Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
final Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {
ServerSocketChannel channel = (ServerSocketChannel)selectionKey.channel();
final SocketChannel accept = channel.accept();
accept.configureBlocking(false);
accept.register(selector,SelectionKey.OP_READ);
} else {
SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
final int read = socketChannel.read(byteBuffer);
if (read != -1) {
System.out.println(new String(byteBuffer.array()));
}
}
iterator.remove();
}
}
}
}