从零开始学Java之什么是BIO阻塞式IO模型?

2023年 10月 13日 104.3k 0

作者:孙玉昌,昵称【一一哥】,另外【壹壹哥】也是我哦

千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者

前言

经过前面的几篇文章,壹哥就把IO流的基本用法给大家介绍完毕了。可以说,IO流是我们从零开始学Java系列以来,API最为繁多的一个知识块,所以这对初学者来说就有比较大的学习难度。而且IO流在不同的发展阶段,还经历了几大不同的分类,比如BIO、NIO和AIO。所以壹哥会再利用几篇文章,来给大家简单说一下这几种分类的区别,毕竟这些内容在面试时也是IO流中的一个考察重点。但大家要注意,壹哥这几篇文章,并不会特别详细地介绍NIO与AIO的内容,后面我会专门出一个NIO与AIO新技术的专栏,敬请大家持续关注壹哥哦。

------------------------------前戏已做完,精彩即开始----------------------------

全文大约【2200】 字,不说废话,只讲可以让你学到技术、明白原理的纯干货!本文带有丰富的案例及配图视频,让你更好地理解和运用文中的技术概念,并可以给你带来具有足够启迪的思考......

配套开源项目资料

Github: github.com/SunLtd/Lear…

Gitee: gitee.com/sunyiyi/Lea…

一. BIO-阻塞式IO

1. BIO简介

BIO全称为Blocking IO,即阻塞式IO,这是Java中最传统的IO模型之一。壹哥之前带大家学习的InputStream、OutputStream、Reader、Writer等字节流和字符流都属于BIO模型。

BIO模型是最原始的一种I/O模型,它是一种阻塞式的I/O,即在读取或写入数据时会阻塞当前的线程,直到有数据可用或者写入完成。也就是说,当输入流没有数据可读时,应用程序会阻塞等待;当输出流的缓冲区已满时,应用程序也会阻塞等待。

2. BIO缺点

BIO最大的问题在于其阻塞模型。每个客户端连接都需要一个线程来处理,当多个客户端连接到服务器端时,如果有某个客户端连接阻塞在某个IO操作上,那么整个服务器端都会被阻塞,无法处理其他客户端的连接请求。尤其是当并发连接数较大时,会导致线程数量过多,占用过多的系统资源,从而影响应用程序的性能。所以这种阻塞模型在高并发访问情况下,很容易出现性能瓶颈。除此之外,BIO模型还存在以下缺点:

  • 线程开销大。每个连接都需要一个独立的线程来处理,当连接数量增加时,线程数量也会增加,线程切换会带来较大的开销。
  • 同步阻塞IO导致IO效率低下。当数据量较大时,每个连接的IO操作都是同步阻塞的,如果读写操作耗时较长,就会阻塞线程,从而导致IO效率低下。
  • 可靠性较差。在客户端请求频繁,服务端响应慢的情况下,会出现大量的超时现象,而超时会导致客户端关闭连接,服务端也会出现一些不稳定的现象。
  • 无法实现真正的高并发。由于BIO模型的限制,无法实现真正的高并发,无法满足现代互联网高并发的需求。
  • 3. 使用场景

    虽然BIO模型在高并发情景下存在很多缺点,但也不是一无是处。BIO模型编程简单,易于理解和实现,适用于连接数较少且对并发要求不高的场景,所以现在开发时仍然很常用,比如:

    • 连接数比较少且响应时间要求不高的场景。例如一些内部管理系统,连接数较少,响应时间要求不高,使用BIO模型可以减少系统的复杂性;
    • 文件传输场景:因文件传输过程中的数据量较大,使用BIO模型可以避免因为内存占用过高而导致的系统崩溃问题;
    • 安全控制场景:BIO模型中的每个连接都需要独立的线程,因此可以更好地控制每个连接的权限和安全性,避免出现安全漏洞。

    总之,BIO模型虽然已经过时,但在某些特定的场景下,仍然可以发挥其优势,因此了解BIO模型的原理和应用场景,对我们来说仍然是有价值的。

    4. BIO基本概念

    在BIO模型中,通常会有以下几个概念:

    • 服务端:提供服务的主机;
    • 客户端:访问服务的主机;
    • 端口:服务的入口,用于区分不同的服务;
    • Socket:客户端和服务端之间的连接,包含了通信双方的IP地址和端口号;
    • ServerSocket:服务端监听特定端口的对象,用于等待客户端的连接请求。

    接下来壹哥会利用这几个概念,带大家实现一个客户端与服务端通信的案例,我们继续往下看。

    5. 代码案例

    接下来,壹哥会设计一个Socket客户端与ServerSocket服务端通信的案例,来给大家演示BIO模型的基本实现。

    5.1 服务端代码

    壹哥首先创建一个ServerSocket服务端的代码案例,在这个例子中,我们将创建一个简单的服务端程序,它监听9999端口,等待客户端的连接请求,并能够接收客户端发送的消息。

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @author 一一哥Sun
     */
    public class Demo14 {
        public static void main(String[] args) throws IOException {
            // 创建ServerSocket对象,监听9999端口
            try (ServerSocket serverSocket = new ServerSocket(9999)) {
                System.out.println("服务端启动成功,等待客户端连接...");
                while (true) {
                    // 监听客户端的连接请求
                    Socket socket = serverSocket.accept();
                    System.out.println("客户端连接成功,客户端地址:" + socket.getInetAddress());
                    // 读取客户端发来的消息
                    InputStream inputStream = socket.getInputStream();
                    // 创建BufferedReader对象
                    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                    String message = reader.readLine();
                    System.out.println("客户端发来的消息:" + message);
                    // 关闭资源
                    reader.close();
                    inputStream.close();
                    socket.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    在上面的代码中,我们通过ServerSocket监听9999端口,等待客户端的连接请求。当客户端连接成功后就可以获取输入流,并使用BufferedReader读取客户端发送的消息。接着,我们将服务器的消息通过输出流发送给客户端,最后再关闭所有相关的资源。

    5.2 客户端代码

    接下来我们再编写一个Socket客户端的代码案例,如下所示:

    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.net.Socket;
    
    /**
     * @author 一一哥Sun
     */
    public class Demo15 {
        public static void main(String[] args) {
            try {
                // 创建客户端对象,监听9999端口
                Socket socket = new Socket("localhost", 9999);
                // 创建输出流对象
                OutputStream outputStream = socket.getOutputStream();
                BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));
                writer.write("hello,这里是客户端");
                // 这里需要使用flush刷新,否则信息可能发不出去
                writer.flush();
                writer.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    注意,壹哥在这里用到了Socket这样的API,后面我会专门再讲解Socket编程,此处大家暂时先跟着练习一下吧。

    ------------------------------正片已结束,来根事后烟----------------------------

    二. 结语

    在今天的这篇文章中,壹哥给大家梳理总结了BIO阻塞式IO模型的特性及其缺点,今天的重点内容如下:

    • 我们之前学习的字节流、字符流等都属于BIO模型;
    • BIO模型是一种阻塞式的IO流,即在读取或写入数据时会阻塞当前的线程,直到有数据可用或者写入完成;
    • BIO阻塞模型在高并发访问情况下,很容易出现性能瓶颈。

    在下一篇文章中,壹哥会给大家介绍NIO非阻塞式的IO模型,欢迎大家继续关注哦。另外如果你独自学习觉得有很多困难,可以加入壹哥的学习互助群,大家一起交流学习。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论