一文搞懂什么是BIO

BIO

BIO英文全名是 blocking IO,也叫做 阻塞IO,是最容易理解、最容易实现的IO工作方式。

1.1、什么是阻塞IO(BIO)

当我们在谈论阻塞IO(Blocking IO)时,我们指的是一种输入输出方式,其中线程正在进行IO操作时会被阻塞(即暂停运行),直到IO 操作完成。这种阻塞是同步的,也就是说线程会等待IO操作完成后再继续执行后续的任务。

在阻塞IO中,当一个线程调用IO操作(如读取或写入数据)时,如果没有数据可用或无法立即完成IO操作,线程会被挂起,直到满足操作条件。这种挂起意味着线程无法执行其他任务,因为它一直等待IO操作完成。

举个例子说明,假设一个线程负责从网络套接字读取数据,Socket就是网络套接字。当线程调用读取数据的方法时,如果没有数据可用,线程将被阻塞,直到有数据可读为止。在此期间,线程无法执行其他任务,他会一直等待直到数据到达或IO操作超时。

阻塞IO的特点是简单易懂,但也存在一些问题。其他一个问题是当有多个IO操作需要处理时,每个操作都会阻塞对应的线程,导致线程的浪费。在高并发或大规模的应用程序中,这可能会导致性能的下降,因为线程的创建和上下文切换会带来额外的开销。

为了解决阻塞IO的性能问题,Java引入了非阻塞IO(Non-blocking IO)模型,例如NIO(New IO)和NIO.2。这些模型使用了事件驱动的方式,通过单个线程处理多个IO操作。当一个IO操作无法立即完成时,线程不会被阻塞,而是继续执行其他任务,等待IO操作完成后再处理。这种方式能更有效地利用系统资源,并提高并发处理能力。

综上所述,阻塞IO是一种简单易懂的IO操作方式,但在高并发或大规模应用程序中可能存在性能问题。了解阻塞IO的概念可以帮助我们理解其他IO模型的工作原理和优势。

1.2、BIO工作原理

当一个客户端请求到达服务器时,服务器会为该请求创建一个新的线程。这个线程将负责处理该请求的所有IO操作,包括读取请求数据、处理请求、发送响应等。这种一对一的线程模型在简单的应用场景下可能是可行的,但当并发请求增加时,线程的数量也会相应增加。

大量线程的创建和管理开销较大,会消耗大量的系统资源,包括内存和CPU。每个线程都需要占用一定的内存空间,并且线程之间的切换也需要消耗CPU资源。如果并发请求非常高,线程的数量可能会过多,导致系统资源不足,甚至引发性能下降、系统崩溃等问题。

BIO的优点是通俗易懂,适用于一些处理少量应发请求的简单服务器,比如:单线程服务器、简单的客户端-服务器通信等。对于高并发、大规模的网络应该程序,BIO模型可能无法满足要求,这会使得线程创建和管理开销太大。

1.3、BIO服务器

当谈到编写一个BIO(Blocking I/O)程序时,我们可以创建一个简单的服务器,它能够接受客户端连接请求并处理这些请求。下面是一个使用java socket编写的BIO服务器简单示例:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class BioServer {

    public static void main(String[] args) {
        int port = 8080; // 服务器监听的端口号

        try {
            ServerSocket serverSocket = new ServerSocket(port);
            System.out.println("服务器启动,监听端口:" + port);

            while (true) {
                // 等待客户端连接
                Socket socket = serverSocket.accept();
                System.out.println("客户端连接成功,地址:" + socket.getInetAddress() + ":" + socket.getPort());

                // 创建线程处理客户端请求
                new Thread(() -> {
                    try {
                        // 获取输入流和输出流
                        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                        PrintWriter writer = new PrintWriter(socket.getOutputStream());

                        // 读取客户端发送的数据
                        String request = reader.readLine();
                        System.out.println("接收到客户端数据:" + request);

                        // 处理请求并返回响应
                        String response = "Hello, client!";
                        writer.println(response);
                        writer.flush();
                        System.out.println("发送响应给客户端:" + response);

                        // 关闭连接
                        socket.close();
                        System.out.println("客户端连接关闭");
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这个示例程序创建了一个ServerSocket来监听指定端口(这里使用8080)。在主循环中,通过调用accept()方法等待客户端的连接请求,没有连接的时候,程序会一直阻塞在这里,直到收到客户端连接请求。一旦客户端连接成功,程序会创建一个新的线程来处理该客户端的请求。

在处理线程中,我们获取与客户端连接的输入流和输出流,使用BufferedReader来读取客户端发送的数据,并使用PrintWriter来进行客户端发送响应。这里处理逻辑非常简单,及仅仅返回一个固定的字符作为响应。

当请求处理结束后,关闭与客户端的连接,并继续等待下一个客户端的连接。

需要注意,这个示例程序是单线程的。当有新的客户端连接时,都会创建一个新的线程来处理请求。这种方式仅适用于简单的应用场景,在高并发下,会创建大量的线程,导致性能下降。

1.4、BIO客户端

如果想发送消息到上面的BIO服务器,我们可以使用一个简单的Socket客户端来连接服务器并发送请求。以下是一个示例代码:

import java.io.*;
import java.net.Socket;

public class BioClient {

    public static void main(String[] args) {
        String serverAddress = "localhost"; // 服务器地址
        int serverPort = 8080; // 服务器端口号

        try {
            // 连接服务器
            Socket socket = new Socket(serverAddress, serverPort);
            System.out.println("连接服务器成功");

            // 获取输入流和输出流
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter writer = new PrintWriter(socket.getOutputStream());

            // 发送请求
            String request = "Hello, server!";
            writer.println(request);
            writer.flush();
            System.out.println("发送请求给服务器:" + request);

            // 接收响应
            String response = reader.readLine();
            System.out.println("接收到服务器响应:" + response);

            // 关闭连接
            socket.close();
            System.out.println("连接关闭");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

在这个示例程序中,BIO客户端与服务器的通信过程如下:

  1. 创建一个Socket对象,指定服务器地址和端口号。
  2. 通过调用Socket对象的getInputStream()方法和getOutputStream()方法,分别获取与服务器连接的输入流和输出流。输入流用于接收服务器的响应,输出流用于向服务器发送请求。
  3. 构造一个请求消息,例如Hello, server!。
  4. 使用输出流的println()方法将请求消息发送给服务器,并调用flush()方法确保消息被立即发送到服务器。
  5. 使用输入流的readLine()方法来读取服务器发送的响应消息。
  6. 打印接收到的服务器响应消息。
  7. 关闭与服务器的连接,调用Socket对象的close()方法。

你可以在该示例程序中修改请求消息和服务器地址、端口号,以适应你的实际情况。

运行实例

先启动服务器,进行端口监听,再启动客户端,连接到指定服务器地址的端口

#BioServer输出台显示

服务器启动,监听端口:8080
客户端连接成功,地址:/127.0.0.1:60038
接收到客户端数据:Hello, server!
发送响应给客户端:Hello, client!
客户端连接关闭

#BioClient输出台显示

连接服务器成功
发送请求给服务器:Hello, server!
接收到服务器响应:Hello, client!
连接关闭

结尾

尽管阻塞IO模型简单易懂,但在高并发或大规模的网络应用程序中,使用该模型可能会遇到线程创建和管理开销过大的问题。因此,在需要高并发处理的场景下,阻塞IO模型并不是最理想的选择。

为了解决这个问题,可以考虑使用其他IO模型,如非阻塞IO(NIO)或基于NIO的框架(如Netty)。

非阻塞IO模型通过使用非阻塞的IO操作和事件轮询机制,允许程序在等待IO操作完成的同时继续执行其他任务,从而提高了系统的并发能力。而基于NIO的框架则提供了更高级别的抽象和更灵活的IO操作方式,例如使用选择器(Selector)来管理多个IO通道,实现单线程处理多个IO连接。

这些模型可以使用较少的线程处理多个请求,并且能更好地利用系统资源,提高并发处理能力。

非阻塞IO和基于NIO的框架具有以下优势:

  1. 更高的并发性能:不像阻塞IO模型那样频繁地创建和管理线程,,非阻塞IO和基于NIO的框架能够通过一个线程处理多个IO连接,减少了线程创建和管理的开销,从而提高了系统的并发性能。

  2. 更灵活的IO操作方式:非阻塞IO和基于NIO的框架提供了更灵活的IO操作方式,例如事件驱动的编程模型和选择器机制,使得程序能够更高效地管理和处理多个IO连接。

  3. 资源利用率更高:更加节约资源,由于非阻塞IO和基于NIO的框架使用了较少的线程,可以更有效地利用系统资源,避免了线程创建和上下文切换的开销。

在选择适合的IO模型时,需要根据具体的应用场景和性能需求来权衡各种模型的优劣。阻塞IO模型适用于简单的、低并发的应用场景,而非阻塞IO和基于NIO的框架则更适合需要处理大量并发连接的高性能应用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,576评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,515评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,017评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,626评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,625评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,255评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,825评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,729评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,271评论 1 320
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,363评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,498评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,183评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,867评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,338评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,458评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,906评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,507评论 2 359

推荐阅读更多精彩内容