找回密码
 立即注册
首页 业界区 业界 理解tomcat中的BIO、NIO、AIO、ARP

理解tomcat中的BIO、NIO、AIO、ARP

柯惠心 2025-6-2 00:44:14
理解tomcat中的BIO、NIO、AIO、ARP

tomcat作为springboot中默认的web容器,了解tomcat的运转可以帮助我们更好的去调整tomcat的参数达到更好的性能
1. 前置知识


  • I/O就是Input/Output,收别人的数据到本机叫Input,本级发数据出去叫Output
  • 网络I/O请求会先到网卡然后到内核态再到用户态
  • CPU比内存快、内存比硬盘、网卡等外设快
  • 所有I/O操作需要被加载到用户态内存,用户态程序才能直接操作
  • 想要效果高,必须让所有的资源都不闲置
  • tomcat不处理请求,会接受请求,转发到具体的容器中
  • 一个socket连接代表一个客户端,一个socket可以发送多份请求不断开
2. scoket测试工具

启动程序是jar包,必须要有jre环境
链接:https://sockettest.sourceforge.net
1.png

3. BIO 同步阻塞IO

每一个socket连接后,tomcat都会有一个线程去全程去陪伴,把请求转发到具体的容器中后,这个线程还在阻塞,等待容器返回数据,只有socket连接断开了,才会回收这个线程。tomcat7或以下默认,比较简单、稳定,适合连接数比较少的
模拟代码如下:
  1. public class BioServer {
  2.    
  3.     static ExecutorService executorService = Executors.newCachedThreadPool();
  4.     public static void main(String[] args) {
  5.         try {
  6.             // 启动服务,绑定8080端口
  7.             ServerSocket serverSocket = new ServerSocket();
  8.             serverSocket.bind(new InetSocketAddress(8080));
  9.             System.out.println("开启服务");
  10.             while (true){
  11.                 System.out.println("等待客户端建立连接");
  12.                 // 监听8080端口,获取客户端连接
  13.                 Socket socket = serverSocket.accept(); //阻塞
  14.                 System.out.println("建立连接:"+socket);
  15.                 executorService.submit(()->{
  16.                     //业务处理
  17.                     try {
  18.                         handler(socket);
  19.                     } catch (IOException e) {
  20.                         e.printStackTrace();
  21.                     }
  22.                 });
  23.             }
  24.         } catch (IOException e) {
  25.             e.printStackTrace();
  26.         } finally {
  27.             //TODO 资源回收
  28.         }
  29.     }
  30.     private static void handler(Socket socket) throws IOException {
  31.         byte[] bytes = new byte[1024];
  32.         System.out.println("等待读取数据");
  33.         int read = socket.getInputStream().read(bytes); // 阻塞
  34.         if(read !=-1) {
  35.             System.out.println("读取客户端发送的数据:" +
  36.                     new String(bytes, 0, read));
  37.         }
  38.     }
  39. }
复制代码
4. NIO 同步非阻塞

一个socket连接过来,会经历以下步骤

  • LimitLatch:连接控制器,负责维护连接数计算,连接数默认是 8192,达到这个阀值后,就会拒绝连接请求。如果要调整修改配置文件server.tomcat.max-connections属性
  • Acceptor:Acceptor 跑在一个单独的线程里,它在一个死循环里调用 accept 方法来接收新连接,一旦有新的连接请求到来,accept 方法返回一个 Channel 对象,接着把 Channel 对象交给 Poller 去处理
  • Poller:Poller 的本质是一个 Selector,也跑在单独线程里。Poller 在内部维护一个 Channel 数组,它在一个死循环里不断检测 Channel 的数据就绪状态,一旦有 Channel 可读,就生成一个 SocketProcessor 任务对象扔给Executor 去处理
  • Executor: Executor 就是线程池,负责运行 SocketProcessor 任务类,SocketProcessor 的 run 方法会调用Http11Processor 来读取和解析请求数据。Http11Processor 是应用层协议的封装,它会调用容器获得响应,再把响应通过 Channel 写出
2.png

tomcat8及以上默认, springboot2.3.12.RELEASE内嵌tomcat是9.0.46版本默认也是这个
模拟代码:
  1. public class NioServer {
  2.     public static void main(String[] args)  {
  3.         List<SocketChannel> list = new ArrayList<>();  // 缓存所有的socket
  4.         ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 缓存区的大小
  5.         try {
  6.             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  7.             // 监听8080
  8.             serverSocketChannel.bind(new InetSocketAddress(8080));
  9.             // channel非阻塞
  10.             serverSocketChannel.configureBlocking(false);
  11.             System.out.println("NioServer 启动....");
  12.             while (true){
  13.                 // 非阻塞
  14.                 SocketChannel socketChannel = serverSocketChannel.accept();
  15.                 Thread.sleep(1000);
  16.                 if(socketChannel == null){
  17.                     System.out.println("没有新的客户端建立连接");
  18.                 }else {
  19.                     System.out.println("新的客户端建立连接");
  20.                     // channel非阻塞
  21.                     socketChannel.configureBlocking(false);
  22.                     // 将新的socket添加到 list
  23.                     list.add(socketChannel);
  24.                 }
  25.                 //遍历所有的socket
  26.                 for(SocketChannel channel:list){
  27.                     //非阻塞
  28.                     int read = channel.read(byteBuffer);
  29.                     if(read >0) {
  30.                         //读模式
  31.                         byteBuffer.flip();
  32.                         System.out.println("读取客户端发送的数据:" +new String(byteBuffer.array(),0,read));
  33.                         byteBuffer.clear();
  34.                     }
  35.                 }
  36.             }
  37.         } catch (Exception e) {
  38.             e.printStackTrace();
  39.         }
  40.     }
  41. }
复制代码
5. AIO异步非阻塞

NIO 和 AIO(NIO2) 最大的区别是,一个是同步一个是异步。异步最大的特点是,应用程序不需要自己去触发数据从内核空间到用户空间的拷贝。
3.png

没有 Poller 组件,也就是没有 Selector。在异步 I/O 模式下,Selector 的工作交给
内核来做了。
Linux 内核没有很完善地支持异步 I/O 模型,因此 JVM 并没有采用原生的 Linux 异步 I/O,而是在应用层面通过 epoll 模拟了异步 I/O 模型。因此在 Linux 平台上,Java NIO 和 Java NIO2 底层都是通过 epoll 来实现的,但是 Java NIO 更加简单高效。如果你的 Tomcat 跑在 Linux 平台上,建议不使用NIO2
模拟代码:
  1. public class AioServer {
  2.     public AsynchronousServerSocketChannel serverSocketChannel;
  3.     public static void main(String[] args) throws Exception {
  4.         new AioServer().listen();
  5.         Thread.sleep(Integer.MAX_VALUE);
  6.     }
  7.     private void listen() throws IOException {
  8.         //1. 创建一个线程池
  9.         ExecutorService es = Executors.newCachedThreadPool();
  10.         //2. 创建异步通道群组
  11.         AsynchronousChannelGroup acg = AsynchronousChannelGroup.withCachedThreadPool(es, 1);
  12.         //3. 创建服务端异步通道
  13.         serverSocketChannel = AsynchronousServerSocketChannel.open(acg);
  14.         //4. 绑定监听端口
  15.         serverSocketChannel.bind(new InetSocketAddress(8080));
  16.         System.out.println("AioServer 启动....");
  17.         //5. 监听连接,传入回调类处理连接请求
  18.         serverSocketChannel.accept(this, new CompletionHandler() {
  19.             //
  20.             //            //具体处理连接请求的就是completed方法,它有两个参数:第一个是异步通道,第二个就是上面传入的AioServer对象
  21.             @Override
  22.             public void completed(AsynchronousSocketChannel socketChannel, AioServer attachment) {
  23.                 try {
  24.                     if (socketChannel.isOpen()) {
  25.                         System.out.println("接收到新的客户端的连接,地址:"
  26.                                 + socketChannel.getRemoteAddress());
  27.                         final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
  28.                         //调用 read 函数读取客户端发送的数据
  29.                         socketChannel.read(byteBuffer, socketChannel,
  30.                                         new CompletionHandler<Integer, AsynchronousSocketChannel>() {
  31.                                             @Override
  32.                             public void completed(Integer result, AsynchronousSocketChannel attachment) {
  33.                                 try {
  34.                                     //读取请求,处理客户端发送的数据
  35.                                     byteBuffer.flip();
  36.                                     String content = Charset.defaultCharset()
  37.                                             .newDecoder().decode(byteBuffer).toString();
  38.                                     System.out.println("服务端接受到客户端发来的数据:" + content);
  39.                                 } catch (CharacterCodingException e) {
  40.                                     e.printStackTrace();
  41.                                 }
  42.                             }
  43.                             @Override
  44.                             public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
  45.                                 exc.printStackTrace();
  46.                                 try {
  47.                                     attachment.close();
  48.                                 } catch (IOException e) {
  49.                                     e.printStackTrace();
  50.                                 }
  51.                             }
  52.                         });
  53.                     }
  54.                 } catch (IOException e) {
  55.                     e.printStackTrace();
  56.                 }finally {
  57.                     //当有新的客户端接入的时候,直接调用accept的方法
  58.                     attachment.serverSocketChannel.accept(attachment, this);
  59.                 }
  60.             }
  61.             @Override
  62.             public void failed(Throwable exc, AioServer attachment) {
  63.                 exc.printStackTrace();
  64.             }
  65.         });
  66.     }
  67. }
复制代码
6. APR异步非阻塞

APR方式全名叫Apache Portable Runtime,需要额外去下载安装配置,NIO2是调用java库去实现异步的,而ARP是直接通过JNI (Java Native Interface)去操作系统是实现异步,APR 能够使用高级 IO 功能 (如sendfile, epoll, OpenSSL),sendfile主要是对静态文件提升很大,换APR也主要是这个原因其他的提升也不是特别大
附上对比图
4.png

springboot配置apr教程:https://www.jianshu.com/p/f716726ba340

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册