文章目录

  • 一.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进程能不能立即响应信号】
  1. 同步:同步指的是调用者发起IO调用后,IO进程在处理完成之前,都不返回信号。
  2. 异步:异步指的是调用者发起IO调用后,IO进程立即返回接受信号,此时调用者和IO进程都在并发执行各自的任务。当IO进程完成任务后,通过回调机制通知调用者。
3.阻塞与非阻塞【侧重于调用者是否需要等待IO进程】
  1. 阻塞:调用者发起IO调用之后,需要等待IO进程完成任务,需要挂起当前进程。
  2. 非阻塞:调用者发起IO调用之后,调用者可以继续执行其他任务而不需要挂起。
4.三种IO模型

三种IO模型即为BIO【同步阻塞IO】、NIO【同步非阻塞IO】、AIO【异步非阻塞IO】。
首先可以先简单地体会一下三种IO模型的情景:假设你需要等待开水的煮开:

  1. 同步阻塞:你蹲在开水壶旁边,什么都不做,等着开水的煮开。
  2. 同步非阻塞:你跑去看书学习,隔一段时间来看看开水是否煮开。
  3. 异步非阻塞:你跑去看书学习,当开水煮开的时候会发出响声,听到响声后你再回来。

二.Java的BIO、NIO和AIO

1.BIO【Blocking I/O】- 同步阻塞IO

在BIO模式下,数据的写入和读取都必须阻塞在一个线程中执行,在写入完成或读取完成前,线程阻塞。

客户端-服务器 链路一一对应模式

在传统的BIO中,一个客户端请求服务器后,服务器会经过Sokcet启动一条链路将其连接并且处理,该链路的IO操作的同步阻塞的,所以该客户端和服务器的连接不可被其他客户端所使用,只能够等待当前的客户端操作完成后释放掉当前连接。

Bi 怎么接入java_非阻塞

代码实现
  • 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开始:

  • 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。
  • 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。

Bi 怎么接入java_Bi 怎么接入java_02

3.Selector

选择器可以让单个线程处理多个通道,达到复用的目的:

Bi 怎么接入java_NIO_03

4.NIO整体架构图

Bi 怎么接入java_BIO_04

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的改进版,主要基于事件和回调机制来实现异步处理。