详解Java中的BIO、NIO、AIO

2023-10-26

1、 详解Java中的BIO、NIO、AIO

1.1、引言

IO流是Java中比较难理解的一个知识点,但是IO流在实际的开发场景中经常会使用到,比如Dubbo底层就是NIO进行通讯。本文将介绍Java发展过程中出现的三种IO:BIO、NIO以及AIO,重点介绍NIO。

1.2、什么是BIO

BIO即同步阻塞IO,实现模型为一个连接就需要一个线程去处理。这种方式简单来说就是当有客户端来请求服务器时,服务器就会开启一个线程去处理这个请求,即使这个请求不干任何事情,这个线程都一直处于阻塞状态。

BIO模型有很多缺点,最大的缺点就是资源的浪费。想象一下如果QQ使用BIO模型,当有一个人上线时就需要一个线程,即使这个人不聊天,这个线程也一直被占用,那再多的服务器资源都不管用。

1.3、BIO代码实践

使用 BIO 模型编写一个服务器端,监听 6666 端口,当有客户端连接时,就启动一个线程与之通讯。
要求使用线程池机制改善,可以连接多个客户端.
服务器端可以接收客户端发送的数据(telnet 方式即可)。

在这里插入图片描述

代码演示:

package com.atguigu.bio;

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class BIOServer {
    public static void main(String[] args) throws Exception {
        //线程池机制
        //思路
        //1. 创建一个线程池
        //2. 如果有客户端连接,就创建一个线程,与之通讯(单独写一个方法)
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        //创建ServerSocket
        ServerSocket serverSocket = new ServerSocket(6666);
        System.out.println("服务器启动了");
        while (true) {
            System.out.println("线程信息 id =" + Thread.currentThread().getId() + " 名字=" +      
            Thread.currentThread().getName());
            //监听,等待客户端连接
            System.out.println("等待连接....");
            final Socket socket = serverSocket.accept();
            System.out.println("连接到一个客户端");

            //就创建一个线程,与之通讯(单独写一个方法)
            newCachedThreadPool.execute(new Runnable() {
                public void run() { //我们重写
                    //可以和客户端通讯
                    handler(socket);
                }
            });
        }
    }

    //编写一个handler方法,和客户端通讯
    public static void handler(Socket socket) {
        try {
            System.out.println("线程信息 id =" + Thread.currentThread().getId() + " 名字=" + 
            Thread.currentThread().getName());
            byte[] bytes = new byte[1024];
            //通过socket 获取输入流
            InputStream inputStream = socket.getInputStream();
            //循环的读取客户端发送的数据
            while (true) {
                System.out.println("线程信息 id =" + Thread.currentThread().getId() + " 名字=" +    
                Thread.currentThread().getName());
                System.out.println("read....");
               int read =  inputStream.read(bytes);
               if(read != -1) {
                   System.out.println(new String(bytes, 0, read
                   )); //输出客户端发送的数据
               } else {
                   break;
               }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            System.out.println("关闭和client的连接");
            try {
                socket.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

演示
快捷键win+R打开cmd窗口,根据上面代码拼写6666端口
输入 telnet 127.0.0.1 6666
进入黑窗口

在这里插入图片描述
输入ctrl+]
向服务端发送文本
send hello world
在这里插入图片描述
服务端接收的

在这里插入图片描述
当有
多个cmd窗口时,则创建多一个线程与之对应
退出时,cmd输入
quit
服务端显示
在这里插入图片描述

2、什么是NIO

BIO是阻塞的,如果没有多线程,BIO就需要一直占用CPU,而NIO则是非阻塞IO,NIO在获取连接或者请求时,即使没有取得连接和数据,也不会阻塞程序。NIO的服务器实现模式为一个线程可以处理多个请求(连接)。
NIO有几个知识点需要掌握,Channel(通道),Buffer(缓冲区), Selector(多路复用选择器)。
Channel既可以用来进行读操作,又可以用来进行写操作。NIO中常用的Channel有FileChannel
、SocketChannel、ServerSocketChannel、DatagramChannel。

Buffer缓冲区用来发送和接受数据。

Selector 一般称为选择器或者多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。在javaNIO中使用Selector往往是将Channel注册到Selector中。

在这里插入图片描述
下面我通过代码的方式模拟javaNIO的运行流程。

2.1、NIO代码实践

首先贴上NIO的实践代码:

NIO服务端详细的执行过程是这样的:

创建一个ServerSocketChannel和Selector,然后将ServerSocketChannel注册到Selector上
Selector通过select方法去轮询监听channel事件,如果有客户端要连接时,监听到连接事件。
通过channel方法将socketchannel绑定到ServerSocketChannel上,绑定通过SelectorKey实现。
socketchannel注册到Selector上,关心读事件。
Selector通过select方法去轮询监听channel事件,当监听到有读事件时,ServerSocketChannel通过绑定的SelectorKey定位到具体的channel,读取里面的数据。

public class NioServer {
    public static void main(String[] args) throws IOException {
        //创建一个socket通道,并且设置为非阻塞的方式
        ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(9000));
        //创建一个selector选择器,把channel注册到selector选择器上
        Selector selector=Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true){
            System.out.println("等待事件发生");
            selector.select();
            System.out.println("有事件发生了");
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();
                handle(key);
            }
        }

    }

    private static void handle(SelectionKey key) throws IOException {
        if (key.isAcceptable()){
            System.out.println("连接事件发生");
            ServerSocketChannel serverSocketChannel= (ServerSocketChannel) key.channel();
            //创建客户端一侧的channel,并注册到selector上
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            socketChannel.register(key.selector(),SelectionKey.OP_READ);
        }else if (key.isReadable()){
            System.out.println("数据可读事件发生");
            SocketChannel socketChannel= (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int len = socketChannel.read(buffer);
            if (len!=-1){
                System.out.println("读取到客户端发送的数据:"+new String(buffer.array(),0,len));
            }
            //给客户端发送信息
            ByteBuffer wrap = ByteBuffer.wrap("hello world".getBytes());
            socketChannel.write(wrap);
            key.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);
            socketChannel.close();
        }
    }
}

客户端代码:NIO客户端代码的实现比BIO复杂很多,主要的区别在于,NIO的客户端也需要去轮询自己和服务端的连接情况。

public class NioClient {
    public static void main(String[] args) throws IOException {
        //配置基本的连接参数
        SocketChannel channel=SocketChannel.open();
        channel.configureBlocking(false);
        Selector selector = Selector.open();
        channel.connect(new InetSocketAddress("127.0.0.1",9000));
        channel.register(selector, SelectionKey.OP_CONNECT);

        //轮询访问selector
        while(true){
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();
                //连接事件发生
                if (key.isConnectable()){
                    SocketChannel socketChannel= (SocketChannel) key.channel();
                    //如果正在连接,则完成连接
                    if (socketChannel.isConnectionPending()){
                        socketChannel.finishConnect();
                    }
                    socketChannel.configureBlocking(false);
                    ByteBuffer buffer = ByteBuffer.wrap("客户端发送的数据".getBytes());
                    socketChannel.write(buffer);
                    socketChannel.register(selector,SelectionKey.OP_READ);
                }else if (key.isReadable()){
                    //读取服务端发送过来的消息
                    read(key);
                }
            }
        }
    }

    private static void read(SelectionKey key) throws IOException {
        SocketChannel socketChannel= (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(512);
        int len=socketChannel.read(buffer);
        if (len!=-1){
            System.out.println("客户端收到信息:"+new String(buffer.array(),0,len));
        }
    }
}

效果大概是这样的:首先服务端等待事件发生,当客户端启动时,服务器端先接受到连接的请求,接着接受到数据读取的请求,读完数据后继续等待。
在这里插入图片描述
客户端发送数据后,获取到了来自服务端的回复。

在这里插入图片描述

2.2、NIO总结

NIO通过一个Selector,负责监听各种IO事件的发生,然后交给后端的线程去处理。NIO相比与BIO而言,非阻塞体现在轮询处理上。BIO后端线程需要阻塞等待客户端写数据,如果客户端不写数据就一直处于阻塞状态。而NIO通过Selector进行轮询已注册的客户端,当有事件发生时才会交给后端去处理,后端线程不需要等待。

3、什么是AIO

AIO是在JDK1.7中推出的新的IO方式–异步非阻塞IO,也被称为NIO2.0,AIO在进行读写操作时,直接调用API的read和write方法即可,这两种均是异步的方法,且完成后会主动调用回调函数。简单来讲,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。

Java提供了四个异步通道:AsynchronousSocketChannel、AsynchronousServerSocketChannel、AsynchronousFileChannel、AsynchronousDatagramChannel。

3.1、AIO代码实践

服务器端代码:AIO的创建方式和NIO类似,先创建通道,再绑定,再监听。只不过AIO中使用了异步的通道。

public class AIOServer {
    public static void main(String[] args) {
        try {
            //创建异步通道
            AsynchronousServerSocketChannel serverSocketChannel=AsynchronousServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(8080));
            System.out.println("等待连接中");
            //在AIO中,accept有两个参数,
            // 第一个参数是一个泛型,可以用来控制想传递的对象
            // 第二个参数CompletionHandler,用来处理监听成功和失败的逻辑
            //  如此设置监听的原因是因为这里的监听是一个类似于递归的操作,每次监听成功后要开启下一个监听
            serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
                //请求成功处理逻辑
                @Override
                public void completed(AsynchronousSocketChannel result, Object attachment) {
                    System.out.println("连接成功,处理数据中");
                    //开启新的监听
                    serverSocketChannel.accept(null,this);
                    handledata(result);
                }
                @Override
                public void failed(Throwable exc, Object attachment) {
                    System.out.println("失败");
                }
            });
            try {
                TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handledata(AsynchronousSocketChannel result) {
        ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
        //通道的read方法也带有三个参数
        //1.目的地:处理客户端传递数据的中转缓存,可以不使用
        //2.处理客户端传递数据的对象
        //3.处理逻辑,也有成功和不成功的两个写法
        result.read(byteBuffer, byteBuffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer result, ByteBuffer attachment) {
                if (result>0){
                    attachment.flip();
                    byte[] array = attachment.array();
                    System.out.println(new String(array));
                }
            }
            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                System.out.println("失败");
            }
        });
    }
}

客户端代码基本上没有太多差别,主要还是实现数据的发送功能

public class AIOClient {
    public static void main(String[] args) {
        try {
            AsynchronousSocketChannel socketChannel=AsynchronousSocketChannel.open();
            socketChannel.connect(new InetSocketAddress("127.0.0.1",8080));
            Scanner scanner=new Scanner(System.in);
            String next = scanner.next();
            ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
            byteBuffer.put(next.getBytes());
            byteBuffer.flip();
            socketChannel.write(byteBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

观察结果:

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

详解Java中的BIO、NIO、AIO 的相关文章

随机推荐

  • Docker学习之Dockerfile

    Dockerfile是为了快速构建镜像 Dockerfile由一行行命令语句组成 并且支持以 开头的注释行 一般而言 Dockerfile分为4个部分 基础镜像信息 维护者信息 镜像操作指令 容器启动时执行指令 直接构建出一个环境容器apa
  • vue el-table里输入框、下拉框校验弹出框提示

    table里面有输入框或下拉框 要执行你的方法进行造作之前需要校验 可以在form里面嵌套表格 参考https blog csdn net qq 45039940 article details 122260642 可以弹出提示框进行提示
  • 利用pyinstaller打包streamlit移植到其他电脑上使用

    先上效果图 由于本人在学习深度学习的过程中 有很多关于python的代码 为此想要思考用部分的python代码作为底层 开发一些简单使用的用户界面 因此考虑采用pyqt gradio streamlit来作为前端页面 pyqt我也会写 但是
  • 因果图设计用例的步骤_用例设计之因果图

    1 因果图的介绍 因果图法是一种利用图解法分析输入的各种组合情况 从而设计测试用例的方法 它适合于检查程序输入条件的各种组合情况 1 1 主要的因果关系 因果图中主要有两种节点 原因节点与结果节点 这两种节点分别有两种状态 0状态 1状态
  • 数据库基础知识

    数据库 1数据库联接操作 左连接 右链接 全链接的操作以及区别 问了数据库的三大范式 一编码 1登录 mysql uroot p 2查询数据库 mysql gt show database 3 选中数据库 use 数据库 4 创建数据库 m
  • 关于java.io.FileNotFoundException: class path resource [spring/springmvc.xml]异常的问题解决

    web xml配置文件如下图所示 但是老是报出以下的异常 org springframework beans factory BeanDefinitionStoreException IOException parsing XML docu
  • 【前端面试题】【Vue】v-if 和v-show有什么区别?

    Q v if 和v show有什么区别 A v if实际上操作的dom元素的销毁或者重建 会切换绑定的事件监听器和部分子组件 v show操作的是切换css的渲染 实际操作的是display属性 none block 如果需要频繁切换组件的
  • buuctf - web - [HCTF 2018]WarmUp

    老样子 F12 检查 发现 source php 被注释掉了 在 url 直接进行访问 可以看到是源代码 发现 high file 泄漏 访问 hint php 可以看到 flag 在那里 回头分析源代码 我们发现最底部的if语句为执行条件
  • Moonbeam社区提议新增收集人维护网络稳定,你怎么看?

    维护网络的稳定性和安全性一直是Moonbeam社区的关注重点 最近 Moonbeam社区成员Jim CertHum发布提案 呼吁新增4个活跃收集人至已有的Moonbeam网络收集人集 提升链上治理和网络出块效率 本次提案的重点 将网络中的活
  • Python安全攻防之第一章渗透测试概述

    渗透测试的具体方法 一 如何绕过CDN获取网站的真实IP地址 通过内部邮箱获取 一般情况下 邮箱服务器位于内网 我们可以通过注册邮箱或者订阅邮件 目的就是让对方给我自己发送邮件 这样 查看邮件的原始邮件头 就会发现邮件服务器的真实IP地址
  • docker - 在 alpine 上安装 MongoDB 的问题

    RUN echo http dl cdn alpinelinux org alpine v3 6 community gt gt etc apk repositories RUN apk update RUN apk add mongodb
  • 2019浙江ACM省赛

    目录 E Sequence in the Pocket J Welcome Party K Strings in the Pocket E Sequence in the Pocket 题意 给定一组数 每次操作 可以把一个数抽出 放在数组
  • postgres的时间转换

    天下苦postgres时间转换久已 最近在操作数据库时 遇到频繁的时间操作 每次弄完了就忘了 今天痛定思痛 下定决心 终于自己也受不了自己的lazy了 对postgres的时间操作进行一下总结 本文竟可能详尽的记录postgres中涉及到d
  • 对称二叉树(Leetcode &101)

    题目 101 对称二叉树 思路 使用层序遍历 遍历当前层的节点时 如该节点的左 右 孩子为空 在list中添加null 否则加入左 右 孩子的值 每遍历完一层则对当前list进行判断 这里判断我用了一个很笨的方法 前面记录下一层节点值时就设
  • tomcat内存(jvm)、并发、缓存优化

    内存调优 rem次调整为高并发内存调优 rem 以下配置为JVM参数调优 set JAVA OPTS server rem 以服务器模式启动 启动速度慢 但更稳定 性能更好 Xms8192M rem 由于本机内存为16G 这里就设置成8G
  • 最全ES6详解及用法

    最全ES6详解及用法 前言 babel babel使用方法 变量的定义 let const this 和作用域 do 顶层对象 global对象 import class JS中的原型 原型语言 prototype proto constr
  • 降低 CPU 占用率的方法

    CPU 占用率和什么有关 答 1 单位时间内执行的指令数目多少 用户时间 2 I O操作时间 等待时间 3 线程调度花费的时间 软 硬中断 优先级调整时间 举个例子 好比一个大人 每次来回可以扛一袋大米 每天的任务要求扛 60 袋 假定来回
  • 软件架构设计

    软件架构设计 一 引言 软件架构的概念 软件架构设计及其生命周期 1 需求分析阶段 需求分析与SA研究二者的联系 2 设计阶段 3 实现阶段 4 构件组装阶段 5 部署阶段 6 后开发阶段 软件架构的重要性 引言 从第一个程序被划分成模块开
  • Unity3D 场景切换

    在Unity3D中 一个场景在运行中切换到另一个场景的方法 1 在Unity中 File gt Build Settings gt 把要切换的场景添加到Scenes In Build下面 也可以Add Open Scenes 添加完关闭这个
  • 详解Java中的BIO、NIO、AIO

    1 详解Java中的BIO NIO AIO 1 1 引言 IO流是Java中比较难理解的一个知识点 但是IO流在实际的开发场景中经常会使用到 比如Dubbo底层就是NIO进行通讯 本文将介绍Java发展过程中出现的三种IO BIO NIO以