NIO:非阻塞IO、IO多路复用、信号驱动IO
三个概念:
1.缓冲区buffer:所有数据都是通过缓冲区进行处理
2.通道channel:可以通过它读取和写入数据,通道是双向,而流是单向的
3.选择器selector:用于管理多个通道,监听注册通道的事件
IO的五种模型一次IO的读取分为两个阶段(写入操作类似):
1.等待内核空间数据准备阶段
2.数据从内核空间拷贝到用户空间
阻塞型IO:两个阶段是连续阻塞的
非阻塞型IO:第一阶段不阻塞的,第二阶段阻塞
IO多路复用:多个IO操作共同使用一个selector(选择器)去询问哪些IO准备好了,selector负责通知哪些数据准备好了的IO,它们再自己去请求内核数据,第一阶段在selector上阻塞,第二阶段在拷贝数据上阻塞
信号驱动IO:用户进程发起读取请求之前先注册一个信号给内核说明自己需要什么数据,这个注册请求立即返回,等内核数据准备好了,主动通知用户进程,用户进程再去请求读取数据,此时,需要等待数据从内核空间拷贝到用户空间再返回,第一阶段不阻塞,第二阶段在拷贝数据上阻塞
异步IO:用户进程发起读取请求后立马返回,当数据完全拷贝到用户空间后通知用户直接使用数据,两阶段都不阻塞
总结:前四种IO均为同步IO,只有最后一种是异步IO
阻塞/非阻塞,更关心的是当前线程是不是被挂起
同步/异步,更关心的是调用结果是不是随着请求结束而返回
为什么不选择异步IO呢?
那是因为异步IO在linux上还不成熟,而我们的服务器通常都是linux,所以现在大部分框架都不是很支持异步IO
BIO、NIO、AIOBIO,阻塞型 IO,也称为 OIO,Old IO。
NIO,New IO,Java 中使用 IO 多路复用技术实现,放在 java.nio 包下,JDK1.4 引入。
AIO,异步 IO,又称为 NIO2,也是放在 java.nio 包下,JDK1.7 引入。
BIO程序(会阻塞当前线程,直到请求结果返回)
public static void main(String[] args) throws Exception {
// 启动服务端,绑定8001端口
ServerSocket serverSocket = new ServerSocket(8001);
System.out.println("server start");
while (true) {
// 开始接受客户端连接
Socket socket = serverSocket.accept();
System.out.println("one client conn" + socket);
// 启动线程处理连接数据
new Thread(()->{
try {
// 读取数据(input和output是相对于客户端的)
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg;
// 使用readLine读取服务端的每一行数据
while ((msg = reader.readLine()) != null) {
System.out.println("receive msg:" + msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
NIO程序(使用IO多路复用技术实现的,多个IO操作共同使用一个selector选择器,去询问哪些IO做好了准备,selector 负责通知那些数据准备好了的 IO,它们再自己去请求内核数据)
public static void main(String[] args) throws IOException {
// 创建一个selector
Selector selector = Selector.open();
// 创建一个serverSocketChannel
ServerSocketChannel channel = ServerSocketChannel.open();
// 绑定8080端口
channel.bind(new InetSocketAddress(8002));
// 设置为非阻塞状态
channel.configureBlocking(false);
// 将channel注册到selector上,并注册Accept事件
channel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("server start");
while (true) {
// 阻塞在select上(第一阶段阻塞)
selector.select();
// 如果使用的是select(timeout)或selectNow()需要判断返回值是否大于0
// 有就绪的Channel
Set selectionKeys = selector.selectedKeys();
// 遍历selectKeys
Iterator iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
// 如果是accept事件
if (selectionKey.isAcceptable()) {
// 强制转换为ServerSocketChannel
ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = ssc.accept();
System.out.println("accept new conn: " + socketChannel.getRemoteAddress());
socketChannel.configureBlocking(false);
// 将SocketChannel注册到Selector上,并注册读事件
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 如果是读取事件
// 强制转换为SocketChannel
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
// 创建Buffer用于读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 将数据读入到buffer中(第二阶段阻塞)
int length = socketChannel.read(buffer);
if (length > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
// 将数据读入到byte数组中
buffer.get(bytes);
// 换行符会跟着消息一起传过来
String content = new String(bytes, "UTF-8").replace("rn", "");
System.out.println("receive msg: " + content);
}
}
iterator.remove();
}
}
}
AIO程序(AIO,异步 IO,相对于 AIO,其它的 IO 模型本质上都是同步 IO)
public static void main(String[] args) throws IOException {
// 启动服务端
AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8003));
System.out.println("server start");
// 监听accept事件,完全异步,不会阻塞
serverSocketChannel.accept(null, new CompletionHandler() {
@Override
public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
try {
System.out.println("accept new conn: " + socketChannel.getRemoteAddress());
// 再次监听accept事件
serverSocketChannel.accept(null, this);
// 消息的处理
while (true) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 将数据读入到buffer中
Future future = socketChannel.read(buffer);
if (future.get() > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
// 将数据读入到byte数组中
buffer.get(bytes);
String content = new String(bytes, "UTF-8");
// 换行符会当成另一条消息传过来
if (content.equals("rn")) {
continue;
}
System.out.println("receive msg: " + content);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("failed");
}
});
// 阻塞住主线程
System.in.read();
}



