Nio2Endpoint组件:Tomcat如何实现异步I/O?

Java 提供了 BIO、NIO 和 NIO.2 这些 API 来实现这些 I/O 模型。BIO 是我们最熟悉的同步阻塞,NIO 是同步非阻塞,那 NIO.2 又是什么呢?NIO 已经足够好了,为什么还要 NIO.2 呢?

NIO 和 NIO.2 最大的区别是,一个是同步一个是异步。我在上期提到过,异步最大的特点是,应用程序不需要自己去触发数据从内核空间到用户空间的拷贝。

为什么是应用程序去“触发”数据的拷贝,而不是直接从内核拷贝数据呢?这是因为应用程序是不能访问内核空间的,因此数据拷贝肯定是由内核来做,关键是谁来触发这个动作。

是内核主动将数据拷贝到用户空间并通知应用程序。还是等待应用程序通过 Selector 来查询,当数据就绪后,应用程序再发起一个 read 调用,这时内核再把数据从内核空间拷贝到用户空间。

需要注意的是,数据从内核空间拷贝到用户空间这段时间,应用程序还是阻塞的。所以你会看到异步的效率是高于同步的,因为异步模式下应用程序始终不会被阻塞。下面我以网络数据读取为例,来说明异步模式的工作过程。

  1. 首先,应用程序在调用 read API 的同时告诉内核两件事情:数据准备好了以后拷贝到哪个 Buffer,以及调用哪个回调函数去处理这些数据。
  2. 之后,内核接到这个 read 指令后,等待网卡数据到达,数据到了后,产生硬件中断,内核在中断程序里把数据从网卡拷贝到内核空间,接着做 TCP/IP 协议层面的数据解包和重组,再把数据拷贝到应用程序指定的 Buffer,最后调用应用程序指定的回调函数

你可能通过下面这张图来回顾一下同步与异步的区别:

 我们可以看到在异步模式下,应用程序当了“甩手掌柜”,内核则忙前忙后,但最大限度提高了 I/O 通信的效率。Windows 的 IOCP 和 Linux 内核 2.6 的 AIO 都提供了异步 I/O 的支持,Java 的 NIO.2 API 就是对操作系统异步 I/O API 的封装。

 

Java NIO.2 回顾


今天我们会重点关注 Tomcat 是如何实现异步 I/O 模型的,但在这之前,我们先来简单回顾下如何用 Java 的 NIO.2 API 来编写一个服务端程序。

public class Nio2Server {

   void listen(){
      //1.创建一个线程池
      ExecutorService es = Executors.newCachedThreadPool();

      //2.创建异步通道群组
      AsynchronousChannelGroup tg = AsynchronousChannelGroup.withCachedThreadPool(es, 1);
      
      //3.创建服务端异步通道
      AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open(tg);

      //4.绑定监听端口
      assc.bind(new InetSocketAddress(8080));

      //5. 监听连接,传入回调类处理连接请求
      assc.accept(this, new AcceptHandler()); 
   }
}

上面的代码主要做了 5 件事情:

  1. 创建一个线程池,这个线程池用来执行来自内核的回调请求。
  2. 创建一个 AsynchronousChannelGroup,并绑定一个线程池。
  3. 创建 AsynchronousServerSocketChannel,并绑定到 AsynchronousChannelGroup。
  4. 绑定一个监听端口。
  5. 调用 accept 方法开始监听连接请求,同时传入一个回调类去处理连接请求。请你注意,accept 方法的第一个参数是 this 对象,就是 Nio2Server 对象本身,我在下文还会讲为什么要传入这个参数。

你可能会问,为什么需要创建一个线程池呢?其实在异步 I/O 模型里,应用程序不知道数据在什么时候到达,因此向内核注册回调函数,当数据到达时,内核就会调用这个回调函数。同时为了提高处理速度,会提供一个线程池给内核使用,这样不会耽误内核线程的工作,内核只需要把工作交给线程池就立即返回了。 

我们再来看看处理连接的回调类 AcceptHandler 是什么样的。


//AcceptHandler类实现了CompletionHandler接口的completed方法。它还有两个模板参数,第一个是异步通道,第二个就是Nio2Server本身
public class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Nio2Server> {

   //具体处理连接请求的就是completed方法,它有两个参数:第一个是异步通道,第二个就是上面传入的NioServer对象
   @Override
   public void completed(AsynchronousSocketChannel asc, Nio2Server attachment) {      
      //调用accept方法继续接收其他客户端的请求
      attachment.assc.accept(attachment, this);
      
      //1. 先分配好Buffer,告诉内核,数据拷贝到哪里去
      ByteBuffer buf = ByteBuffer.allocate(1024);
      
      //2. 调用read函数读取数据,除了把buf作为参数传入,还传入读回调类
      channel.read(buf, buf, new ReadHandler(asc)); 

}

我们看到它实现了 CompletionHandler 接口,下面我们先来看看 CompletionHandler 接口的定义。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值