BIO、NIO、AIO、Netty从简单理解到使用

Java编程中BIO、NIO、AIO是三种不同的I/O(输入/输出)模型,它们代表了不同的I/O处理方式。

Netty就是基于Java的NIO(New Input/Output)类库编写的一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。

先来了解一下基本的三种基本的io模型:

BIO(Blocking I/O,阻塞I/O)

定义 :BIO是Java最传统的I/O模型,基于流的同步阻塞I/O操作。每个连接都会占用一个线程,当进行I/O操作时,线程会被阻塞,直到操作完成。
特点

同步阻塞:每个I/O操作都会阻塞当前线程,直到操作完成。

线程占用:每个连接需要一个独立的线程,线程资源消耗较大。

实现简单:代码实现相对简单,易于理解和编写。
作用:适用于连接数较少且固定的架构,如传统的C/S架构。由于其实现简单、编程直观,因此在一些简单的网络编程场景中仍然被使用。

创建客户端和服务端演示数据接收与传输。

//服务端
public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(9999)) {
            while (true) {
                Socket socket = serverSocket.accept(); // 持续监听新连接
                new Thread(() -> { // 为每个客户端创建独立线程
                    try (BufferedReader reader = new BufferedReader(
                            new InputStreamReader(socket.getInputStream()))) {
                        String line;
                        while ((line = reader.readLine()) != null) {
                            System.out.println("客户端[" + socket.getPort() + "] 消息: " + line);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

//客户端
public static void main(String[] args) {
        System.out.println("客户端启动...");
        try {
            //创建套接字
            Socket socket = new Socket("127.0.0.1", 9999);
            //获取输出流发送消息
            PrintStream ps = new PrintStream(socket.getOutputStream());
            Scanner sc = new Scanner(System.in);
            while (true){
                System.out.println("请输入信息:");
                ps.println(sc.nextLine());
                ps.flush();
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

NIO(Non-blocking I/O,非阻塞I/O)

定义 :NIO是Java 1.4引入的新I/O API,基于通道(Channel)和缓冲区(Buffer)的非阻塞I/O操作。
特点

非阻塞:I/O操作不会阻塞线程,线程可以在等待数据时执行其他任务。

多路复用:通过Selector可以管理多个Channel,提高了资源利用率。

高性能:适用于高并发场景,减少线程开销和上下文切换。
核心组件

缓冲区(Buffer):用于存储数据的固定大小的内存区域,提供了多种类型的缓冲区,如ByteBuffer、CharBuffer等。

通道(Channel):用于数据读写的通道,支持非阻塞模式,与缓冲区配合使用。

选择器(Selector):允许单个线程同时处理多个通道的I/O事件。
作用:适用于连接数较多且连接较短的架构,如高并发的服务器端应用。NIO提供了更高效、更灵活的I/O操作方式,显著提高了系统的并发性能和吞吐量。

//服务端
    public static void main(String[] args) throws Exception {
        //创建连接通道,ServerSocketChannel 绑定端口,监听新的客户端连接请求。它本身不处理任何数据传输。
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 设置通道为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //绑定端口
        serverSocketChannel.bind(new InetSocketAddress(9999));
        //创建 Selector 并注册 ACCEPT 事件
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("NIO 服务端启动...");

        //阻塞等待事件发生
        while (selector.select()>0){
            //获取到所有事件
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            //遍历所有事件
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                //检查是否有新的连接请求
                if(key.isAcceptable()){
                    //为每一个创建新的通道
                    SocketChannel clientChannel = serverSocketChannel.accept();
                    clientChannel.configureBlocking(false);
                    clientChannel.register(selector,SelectionKey.OP_READ);
                }else if(key.isReadable()){
                    //处理读数据
                    SocketChannel channel = (SocketChannel)key.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int bytesRead = 0;
                    while ((bytesRead = channel.read(byteBuffer)) > 0){
                        byteBuffer.flip();
                        String message = new String(byteBuffer.array(),0,bytesRead);
                        System.out.println("接收客户端信息:"+message);
                        byteBuffer.clear();

                        //回写数据给客户端
                        ByteBuffer response = ByteBuffer.wrap(("ECHO: " + message).getBytes());
                        channel.write(response);
                    }
                }
                iterator.remove();
            }
        }
    }

//客户端
public static void main(String[] args) throws Exception {
        // 1. 创建 SocketChannel 并连接服务器
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
        socketChannel.configureBlocking(false); // 非阻塞模式

        System.out.println("NIO 客户端已连接");

        //接收线程
        new Thread(() -> {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while (true) {
                try {
                    int bytesRead = socketChannel.read(buffer);
                    if (bytesRead > 0) {
                        buffer.flip();
                        byte[] data = new byte[buffer.remaining()];
                        buffer.get(data);
                        System.out.println("收到服务端响应: " + new String(data));
                        buffer.clear();
                    }
                } catch (IOException e) {
                    System.out.println("连接已断开");
                    break;
                }
            }
        }).start();

        //发送消息
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        Scanner sc = new Scanner(System.in);
        while (true){
            System.out.println("请输入信息:");
            String msg = sc.nextLine();
            byteBuffer.put(("AA:"+msg).getBytes(StandardCharsets.UTF_8));
            byteBuffer.flip();
            socketChannel.write(byteBuffer);
            byteBuffer.clear();
        }
    }

AIO(Asynchronous I/O,异步I/O)

定义 :AIO是Java 7引入的异步I/O API,基于异步通道(AsynchronousChannel)和异步回调机制。
特点

异步非阻塞:I/O操作是异步的,通过回调处理结果,不阻塞线程。

回调机制:通过回调函数处理I/O操作结果,提高了程序的灵活性。

高性能:适用于高并发、高吞吐量的场景。
工作方式

程序发起一个异步I/O请求,并提供一个回调函数。

程序无需等待I/O操作完成,而是立即返回,继续执行其他任务。

当I/O操作完成后,系统会调用之前提供的回调函数,传递结果或状态信息。
作用:适用于连接数较多且连接时间较长的架构,如高并发的服务器端应用。AIO提供了真正的异步I/O操作方式,进一步提高了系统的并发性能和吞吐量。

Netty

先了解一下netty出现的原因,是干嘛用的?

  • Netty出现的原因:

Java NIO的复杂性:

Java NIO虽然提供了非阻塞IO的能力,但其API设计较为底层,使用起来比较复杂。开发者需要处理大量的细节,如选择器的管理、缓冲区的操作等,这增加了开发的难度和出错的风险。
性能瓶颈:

在高并发场景下,直接使用Java NIO进行网络编程可能会遇到性能瓶颈。例如,选择器的实现和多线程管理的复杂性可能导致性能下降。
缺乏高级功能:

Java NIO仅提供了基础的非阻塞IO机制,缺乏一些高级功能,如协议编解码、连接管理等。这些功能在开发网络应用时非常重要,但实现起来却相对复杂。

  • Netty的具体用途是什么?

简化网络编程:

Netty封装了Java NIO的复杂性,提供了一套简洁易用的API。开发者可以使用这些API快速构建网络应用,而无需关注底层的细节。
提高性能:

Netty采用了异步非阻塞IO模型,并支持零拷贝等技术,可以在保证高性能的同时,减少CPU和内存资源的消耗。这使得Netty在高并发场景下表现尤为出色。
支持多种协议:

Netty内置了对多种协议的支持,如TCP、UDP、HTTP、WebSocket等。开发者可以轻松地使用这些协议构建网络应用,而无需自己实现协议编解码等复杂功能。
灵活的扩展性:

Netty提供了丰富的扩展点,如ChannelHandler、Codec等。开发者可以通过实现这些接口来扩展Netty的功能,以满足特定的业务需求。
广泛的应用场景:

Netty经过广泛的使用和验证,具有高稳定性和可靠性,适用于各种网络应用场景,如分布式系统、微服务架构中的通信组件、实时通讯系统、游戏服务器等。

它提供了丰富的错误处理和恢复机制,能够有效地处理网络通信中的各种异常情况。
netty核心优势:

异步事件驱动、零拷贝、内存池、高度可定制。

  • 核心组件

先大概了解一下 Reactor 模型(单线程、多线程、主从多线程)。

因为Reactor 模式是 Netty 高性能和高并发能力的核心设计基础。Netty 的线程模型、事件驱动机制和异步非阻塞 I/O 都深度依赖 Reactor 模式。

Reactor 模式是一种 事件驱动的设计模式,用于处理高并发的 I/O 请求。其核心思想是 用一个或多个线程监听事件(如连接、读写请求),并将事件分发给对应的处理器异步处理,避免线程阻塞和资源浪费。

Reactor模型的核心组件

事件源(Event Source):指任何可以产生I/O事件的对象,例如网络连接、文件、设备等。

事件循环(Event Loop):Reactor模型的核心,负责监控所有事件源的状态,并在有事件发生时将其分发给相应的处理程序(即回调函数)。

事件处理器(Event Handler):处理特定事件的代码模块,通常实现为回调函数或方法。针对特定的事件源,每一个事件源通常都有一个相应的处理器,用于处理该事件源的I/O事件。

多路复用器(Demultiplexer):用于监视多个事件源并将发生的事件通知给Reactor。常见的实现包括Java的Selector、Linux的epoll等。

Reactor模型的实现方式

单Reactor单线程模型:所有操作(连接、读写)由一个线程完成。设计简单,但在高并发场景下容易成为性能瓶颈。

单Reactor多线程模型:Reactor线程负责监听和分发事件,而事件的处理则交给线程池中的工作线程完成。这种方式能够充分利用多核CPU的处理能力,但在高并发场景下,Reactor线程可能成为性能瓶颈。

主从Reactor多线程模型(Netty 默认模型-高并发):也称为多Reactor多线程模型。主Reactor线程负责监听和分发连接事件,当有新的连接到来时,将其分发给从Reactor线程处理。从Reactor线程负责监听和分发读写事件,并交给线程池中的工作线程处理。这种方式能够进一步提高系统的并发处理能力和可扩展性。

写个netty服务端示例看一下:

// 1. 创建主 Reactor(处理连接)管理一组 EventLoop,每个线程绑定一个 EventLoop(事件循环线程)
EventLoopGroup bossGroup = new NioEventLoopGroup(1);  // 主线程组(通常 1 个线程)
// 2. 创建子 Reactor(处理 I/O)
EventLoopGroup workerGroup = new NioEventLoopGroup();  // 子线程组(默认 CPU 核数 × 2)

//Reactor 模式配置入口	绑定主/子线程组、设置 Channel 类型
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup, workerGroup)  // 绑定主从线程组
       .channel(NioServerSocketChannel.class)  // 设置 Channel 类型(NIO)代表一个 Socket 连接或监听端口
       .childHandler(new ChannelInitializer<SocketChannel>() {
           @Override
           protected void initChannel(SocketChannel ch) {
               ch.pipeline().addLast(new ServerHandler());  // 添加业务处理器,这里需要自己实现
           }
       });

// 3. 绑定端口并启动服务
ChannelFuture future = server.bind(8080).sync();
//等待服务器通道关闭
future.channel().closeFuture().sync();
//关闭通道
finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }

//业务处理器
public class ServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端连接成功...");
    }
   }
  • 组件说明:

Channel:

在Netty中,Channel是一个核心概念,Channel提供了数据的读取和写入操作,以及与远程端点进行通信的能力。

通过Channel可以获取到很多信息,比如:本地地址、远程地址、Channel的EventLoop、Channel的Pipeline等。

Channel 生命周期:

channelRegistered (初始化操作)→ channelActive (连接)→ channelRead(数据接收) → channelInactive(断开)→ channelUnregistered(注销)
Selector:

Selector是Java NIO(New Input/Output)中的一个核心组件,用于监控多个Channel(通道)的状态,例如连接、读、写等事件。在netty中Selector被封装在NioEventLoop中,当Channel注册到Selector并指定感兴趣的事件类型(如连接、读、写等)后,会返回一个SelectionKey,用于表示Selector与Channel之间的关联关系。Selector会不断地轮询其注册的Channel,如果有事件发生,Selector会将相应的SelectionKey放入就绪事件集合中。事件循环线程会从就绪事件集合中取出SelectionKey,并根据事件类型调用相应的ChannelHandler进行处理。处理完成后,事件循环线程会将Channel放回到Selector中,继续等待下一次事件的发生。

EventLoopGroup:

管理一组 EventLoop,通常分为 bossGroup(处理连接)和 workerGroup(处理 I/O)。

配置线程数:new NioEventLoopGroup(4) 表示 4 个线程。
ServerBootstrap:

用于启动服务器端的引导类,配置服务器参数和处理器链配置、启动服务器、关闭服务器。

  1. 配置服务器参数:

线程模型:ServerBootstrap允许您配置两个主要的线程组:bossGroup和workerGroup。

bossGroup用于处理客户端的连接请求,而workerGroup用于处理已连接的客户端的I/O操作。

通道类型:通过channel方法,您可以指定服务器使用的通道类型。对于NIO传输,通常使用NioServerSocketChannel。

通道选项:使用option方法可以设置服务器通道的选项,如SO_BACKLOG(TCP连接请求的最大队列长度)和SO_REUSEADDR(允许地址重用)。

子通道选项:childOption方法用于设置已连接客户端的通道选项,如SO_KEEPALIVE(保持连接活动状态)。

Option:可以应用于Channel或Bootstrap上。

ChannelOption:专门用于配置Channel的参数。

  1. 处理器链配置:

父处理器:通过handler方法,您可以设置处理服务器通道I/O事件的处理器。这通常用于处理连接请求。

子处理器:childHandler方法用于设置处理已连接客户端I/O事件的处理器。这是开发者编写业务逻辑处理代码的地方,通常通过添加一系列的ChannelHandler来实现。

ChannelPipeline用于处理网络事件和数据的流动。负责将一系列的处理逻辑(称为ChannelHandler)串联起来,形成一个处理链,对网络事件进行拦截和处理。

事件处理:ChannelPipeline负责处理入站(Inbound)和出站(Outbound)事件,如数据读取、数据写入、连接建立、连接断开等。

拦截器链:ChannelPipeline内部维护了一个拦截器链(或称为处理器链),每个拦截器(即ChannelHandler)都可以对事件进行处理或拦截。

动态性:ChannelPipeline允许在运行时动态地添加、删除或替换拦截器,从而灵活地扩展和定制网络处理逻辑。

ChannelHandler是ChannelPipeline中的基本处理单元,负责处理或拦截入站和出站事件。每个ChannelHandler都有一个关联的ChannelHandlerContext,通过它可以方便地与其他组件进行交互。ChannelPipeline则是ChannelHandler的容器,负责ChannelHandler的管理和事件拦截。

  1. 启动服务器:

    配置完成后,通过调用bind方法并传入服务器的端口号,ServerBootstrap将启动服务器并绑定到指定的端口。

    bind方法返回一个ChannelFuture对象,您可以使用sync方法等待服务器启动完成。

    ChannelFuture是Netty框架中特有的接口,继承自Java的Future接口。在Netty中,所有的I/O操作都是异步的,ChannelFuture用于在操作完成时通知应用程序,以便应用程序可以执行某些操作或检索操作的结果。

  2. 关闭:

当服务器需要关闭时,可以调用ChannelFuture对象的channel().closeFuture().sync()方法,等待服务器通道关闭。

最后,调用bossGroup和workerGroup的shutdownGracefully方法,以优雅地断开连接并关闭线程组。

ChannelHandlerContext:

ChannelHandlerContext封装了Channel和ChannelPipeline,使得ChannelHandler可以方便地访问和操作ChannelPipeline和Channel。

ChannelHandlerContext提供了许多方法,用于操作和传播事件,如写入数据、刷新数据、触发读事件等。

ChannelHandlerContext允许ChannelHandler将事件传递给ChannelPipeline中的下一个处理器。例如,当一个入站事件(如数据读取)发生时,ChannelHandlerContext可以调用fireChannelRead方法,将事件传递给下一个入站处理器。

相关推荐
多多*9 分钟前
MyBatis-Plus 元对象处理器 @TableField注解 反射动态赋值 实现字段自动填充
java·开发语言·数据库·windows·github·mybatis
Su米苏27 分钟前
SpringBoot敏感数据脱敏怎么处理
java
搬山境KL攻城狮31 分钟前
复杂html动态页面高还原批量导出方案
java·html
程序猿小D32 分钟前
第三百七十二节 JavaFX教程 - JavaFX HTMLEditor
java·服务器·前端
B站计算机毕业设计超人37 分钟前
计算机毕业设计SpringBoot+Vue.js社区智慧养老监护管理平台(源码+文档+PPT+讲解)
java·vue.js·spring boot·后端·毕业设计·课程设计·毕设
勿忘初心122139 分钟前
idea生成自定义Maven原型(archetype)项目工程模板
java·maven·intellij-idea
B站计算机毕业设计超人39 分钟前
计算机毕业设计SpringBoot+Vue.js在线问卷调查系统(源码+文档+PPT+讲解)
java·vue.js·spring boot·后端·毕业设计·课程设计·毕设
java12241 小时前
各种传参形式
java·状态模式
码有余悸1 小时前
Java 连接 Redis 的两种方式
java·redis·bootstrap
Jesslili1 小时前
秒杀系统的常用架构是什么?怎么设计?
java·中间件·架构