当前位置: 首页 > news >正文

JavaIO详解--尽可能将BIO、NIO、AIO讲得通俗易懂

一、BIO概述

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

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

二、BIO代码实践

我们通过socket模拟BIO的实现逻辑

首先建立Server,建立一个ServerSocket对象,绑定端口,然后等待连接,如果连接成功就新建一个线程去处理连接。

public class server {
    private static Socket socket=null;
    public static void main(String[] args) {
        try {
            //绑定端口
            ServerSocket serverSocket=new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8080));
            while (true){
                //等待连接  阻塞
                System.out.println("等待连接");
                socket = serverSocket.accept();
                System.out.println("连接成功");
                //连接成功后新开一个线程去处理这个连接
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        byte[] bytes=new byte[1024];
                        try {
                            System.out.println("等待读取数据");
                            //等待读取数据    阻塞
                            int length=socket.getInputStream().read(bytes);
                            System.out.println(new String(bytes,0,length));
                            System.out.println("数据读取成功");
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

接着建立Client代码

public class Client {
    public static void main(String[] args) {
        Socket socket= null;
        try {
            socket = new Socket("127.0.0.1",8080);
            socket.getOutputStream().write("一条数据".getBytes());
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端的代码就连接一个服务器,然后发出一条数据即可。

这样就实现了一个BIO,但是BIO的缺点实在太明显了,因此在JDK1.4的时候,NIO出现了。

三、NIO概述

BIO是阻塞的,如果没有多线程,BIO就需要一直占用CPU,而NIO则是非阻塞IO,NIO在获取连接或者请求时,即使没有取得连接和数据,也不会阻塞程序。NIO有几个知识点需要掌握,Channel(通道),Buffer(缓冲区), Selector(选择器(多路复用))

Channel既可以用来进行读操作,又可以用来进行写操作。NIO中常用的Channel有FileChannel

、SocketChannel、ServerSocketChannel、DatagramChannel。

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

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

下面我通过代码的方式模拟javaNIO的运行流程

四、NIO代码实践

在BIO的代码中,有两处位置发生了阻塞:1.等待请求时,2.等待数据时

socket = serverSocket.accept();
int length=socket.getInputStream().read(bytes);

而NIO的代码就是要让这两处的阻塞变为非阻塞,Socket本身并不支持NIO,因为java创建了支持NIO的socket,SocketChannelServerSocketChannel。通过configureBlocking方法设置为非阻塞。

ServerSocketChannel是NIO中的通道,ByteBuffer是获取数据的缓冲区,多路复用机制我在这里使用for循环代替,而多路复用机制实际上是通过select/epoll/poll实现的,这需要涉及到底层的知识,当一个socket连接成功时,就将他放入集合中,并不断遍历这个集合观察是否有哪个socket发送了数据。

服务端代码:

public class Server {
    //list用来存放已经连接的socket
    static List<SocketChannel> list=new ArrayList<>();
    //缓冲区,用来获取数据
    static ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
    public static void main(String[] args) {
        try {
            //创建一个socketchannel,并绑定端口
            ServerSocketChannel server=ServerSocketChannel.open();
            server.bind(new InetSocketAddress(8080));
            //设置这个通道为非阻塞,对应于BIO的第一处阻塞
            server.configureBlocking(false);
            while (true){
                //模拟多路复用器,不断查询是否有数据发过来
                for (SocketChannel socketChannel1:list){
                    byteBuffer.clear();
                    int length = socketChannel1.read(byteBuffer);
                    if (length>0){
                        byteBuffer.flip();
                        System.out.println(new String(byteBuffer.array()));
                    }
                }
                //非阻塞的获取连接
                SocketChannel socketChannel = server.accept();
                //如果没有,则等待
                if (socketChannel==null){
                    System.out.println("等待连接");
                    Thread.sleep(1000);
                }else {
                    //如果获取到了一个连接,则把这个连接也设置为非阻塞,对应于BIO的第二处阻塞
                    socketChannel.configureBlocking(false);
                    //将设置连接放进list中,不断轮询是否有消息发出
                    list.add(socketChannel);
                    System.out.println("连接成功");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

客户端代码:

我们不必要求客户端也实现NIO,因此客户端使用BIO实现

public class client {
    public static void main(String[] args) {
        try {
            Socket socket=new Socket("127.0.0.1",8080);
            Scanner scanner=new Scanner(System.in);
            String str=scanner.next();
            socket.getOutputStream().write(str.getBytes());
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

当启动服务器端时,不断输出等待连接,当启动客户端后,输出了连接成功,但此时并未阻塞程序,而是继续等待。

当有数据发送给服务器端时,则接收到了ByteBuffer的数据,程序的所有执行过程都处于非阻塞状态。

五、AIO概述

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

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

六、AIO代码实践

服务器端代码:AIO的创建方式和NIO类似,先创建通道,再绑定,再监听。只不过这里的监听方式用了类似递归的监听方式。在读取数据的操作中,read方法也和NIO有区别。

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();
        }
    }
}

观察结果:

相关文章:

  • 用jedis获取redis连接(集群和非集群状态下)
  • Mysql的索引调优详解:如何去创建索引以及避免索引失效
  • (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路
  • 经验分享:JAVA实习生刚进公司主要做些什么?以及进入职场后我的心理变化
  • oracle迁移mysql数据库注意(转)
  • 实习工作经历:代码在本地明明可以跑通,怎么放到服务器上就不行了呢?
  • 搭建一个包含多种Get请求和Post请求的工具类
  • 一致性hash原理与实现
  • 作为一个程序员需要了解多少网络方面的基础?网络基础总结(不断更新)
  • 四千字从源码分析ConcurrentHashMap的底层原理(JDK1.8)
  • redis学习笔记6--集合类型
  • 都2020年了,你还不知道count(1)和count(*)谁效率更高吗?
  • Linux下PF_PACKET的使用,RARP的server和client程序
  • 面试官:不会真有人不知道什么是线程池吧?
  • 从零搭建基于SpringBoot的秒杀系统(一):项目准备
  • [笔记] php常见简单功能及函数
  • 《用数据讲故事》作者Cole N. Knaflic:消除一切无效的图表
  • bootstrap创建登录注册页面
  • JavaScript设计模式之工厂模式
  • Logstash 参考指南(目录)
  • node 版本过低
  • Python_OOP
  • React-生命周期杂记
  • Vue小说阅读器(仿追书神器)
  • 大整数乘法-表格法
  • 给自己的博客网站加上酷炫的初音未来音乐游戏?
  • 关于Java中分层中遇到的一些问题
  • 检测对象或数组
  • 前端
  • 前端性能优化--懒加载和预加载
  • 推荐一个React的管理后台框架
  • LIGO、Virgo第三轮探测告捷,同时探测到一对黑洞合并产生的引力波事件 ...
  • linux 淘宝开源监控工具tsar
  • ​io --- 处理流的核心工具​
  • ​力扣解法汇总1802. 有界数组中指定下标处的最大值
  • # 深度解析 Socket 与 WebSocket:原理、区别与应用
  • #Z0458. 树的中心2
  • #基础#使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件
  • (1综述)从零开始的嵌入式图像图像处理(PI+QT+OpenCV)实战演练
  • (6)设计一个TimeMap
  • (bean配置类的注解开发)学习Spring的第十三天
  • (iPhone/iPad开发)在UIWebView中自定义菜单栏
  • (顶刊)一个基于分类代理模型的超多目标优化算法
  • (二)Pytorch快速搭建神经网络模型实现气温预测回归(代码+详细注解)
  • (二)斐波那契Fabonacci函数
  • (二)构建dubbo分布式平台-平台功能导图
  • (附源码)ssm基于微信小程序的疫苗管理系统 毕业设计 092354
  • (三)mysql_MYSQL(三)
  • . Flume面试题
  • .apk文件,IIS不支持下载解决
  • .gitignore文件设置了忽略但不生效
  • .net Stream篇(六)
  • .net访问oracle数据库性能问题
  • .NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数
  • .NET使用存储过程实现对数据库的增删改查