Java BIO 原理浅析

本文知识点:

BIO 工作原理

BIO数据流类型

Java Socket使用方法

BIO 模型缺点与优化方法

前言

BIO在Java语言里是一种比较老的网络I/O模型,是阻塞的网络I/O模型,在监听、读取数据、写入数据时都会对调用线程进行阻塞等待内核态完成,读取数据的等待分为等待数据传输数据到用户空间

BIO工作原理图如下:

BIO

BIO以数据流为核心,在读取与写入时都是通过流进行操作

I/O流

InputStream是Java socket中提供的默认读写网络流的接口类,其内部由SocketInputStream实现;在调用read方法时如果流还没有准备完成则会阻塞整个调用线程直到流准备完成。需要写入数据时同样需要将数据写入OutputStream类型的流中,内部由SocketOutputStream实现。

Socket 示例讲解BIO模型工作原理

用文字表述了这么多,怎么使用还是不清楚,下面来看一个简单的使用Java Socket实现BIO模型通信例子,例子实现了一个点对点的文字传输器:
服务端:

public static void main(String args[]) throws IOException {
    ServerSocket serverSocket = new ServerSocket(30888);
    System.out.println("Start accept...");
    Socket socket = serverSocket.accept();
    BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    PrintWriter writer = new PrintWriter(socket.getOutputStream());
    writer.println("Connection Success!");
    writer.flush();
    String message;
    while (!"bye".equalsIgnoreCase(message=reader.readLine())){
        System.out.println("Client message:"+message);
        writer.println(("Service message: "+message));
        writer.flush();
    }
    reader.close();
    writer.close();
    socket.close();
    serverSocket.close();
}

客户端:

public static void main(String args[]) throws IOException, UnknownHostException {
    Socket socket = new Socket(InetAddress.getLocalHost(), 30888);
    PrintWriter writer = new PrintWriter(socket.getOutputStream());
    Scanner scanner = new Scanner(System.in);
    String readline = "";
    Thread thread = new Thread(() -> {
        int size = -1;
        byte[] bytes = new byte[1024];
        StringBuilder sb = new StringBuilder(1024);
        try {
            while ((size = socket.getInputStream().read(bytes, 0, bytes.length)) > 0) {
                String message = new String(bytes, 0, size, "UTF-8");
                sb.append(message);
                if (message.lastIndexOf("\n") > 0) {
                    System.out.println(sb.toString());
                    sb.delete(0, sb.length());
                }
                if (Thread.currentThread().isInterrupted()) {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    });
    thread.start();
    while (!readline.equalsIgnoreCase("bye")) {
        readline = scanner.nextLine();
        writer.println(readline);
        writer.flush();
    }
    scanner.close();
    writer.close();
    socket.close();
    thread.interrupt();
}

例子中没有做过多的异常处理,不能算是一个完整的示例,但还是能说明BIO模型中的一些原理。

首先服务端会调用accept()方法监听配置的端口并返回一个新的Socket实例,在没有新的连接进来时整个线程会被accept()方法阻塞,当有新的连接进来时唤醒阻塞的线程并返回Socket实例,再通过Socket实例的getInputStream()方法获取输入流得到一个InputStream类型实例,调用InputStream实例的read()方法同样会阻塞当前线程,在内核将数据准备完成后唤醒线程读取数据再将数据进行输出。示例中使用轮询的方式一直等待客户端的输入,直到客户端输入“bye”时退出。当然服务端也会返回客户端消息,方式是在上面的Socket实例中通过getOutputStream()方法获取的OutputStream类型实例的write()方法进行输出消息的写入,写入完成后调用OutputStream类型实例的flash()方法刷新数据,这时输出数据将开始传输给客户端。

客户端的启动和服务端的启动稍有不同在于客户端的启动不是先监听,而是先连接,连接上后就可以开始读写数据,读写数据的原理与方法和服务端是一样一样的;示例中加了一个线程处理客户端的消息读取与打印作用在于读取消息时不阻塞数据的写入,让客户端可以一直根据用户的输入状况发送数据,不会因为读取网络数据而阻塞用户的输入导致假死的现象。

BIO 模型缺点

熟话说没有对比就没有伤害,优点与缺点的列举是相对NIO而言;先说下BIO的优点:编程简单,模型便于理解,适用单个大数据对象的高效传输;相反BIO缺点在于对高并发场景下对于线程资源的消耗较高,每一个连接需要使用一条线程单独处理,传输较小对象时存在频繁的线程上下文切换等性能问题。

虽然BIO模型存在性能上的问题,但在如今NIO唱红时期,使用BIO模型通信的服务还是不在少数,主要原因一个是历史遗留问题,第二个是BIO也有它的优点,术业有专攻啊,像文件传输等需要传输大量数据的场景下还是比较适用BIO模型。

BIO 模型优化

BIO 模型是阻塞的,模型本身就决定了在处理多连接的场景时不能使用单线程的方式去处理;有多少连接就需要多少线程,但对于用户来说打开一个连接,再关闭这个连接是常有的事,而这相对应的就是线程的创建与销毁;线程的创建与销毁对于系统资源来说是昂贵的,所以我们可以使用线程池来进行优化。具体的示例就不贴了,篇幅有限。

最后唠嗑说说为什么会有NIO的面世,这也和BIO 模型的优化有关;BIO模型是阻塞的,阻塞就导致不能使用单线程处理多个请求,但如果将BIO 模型修改,调用read() write()方法不再是阻塞的,那这样就可以使用单线程处理多个请求了;而这样的优化正是NIO的精髓所在。

往期推荐:

计算机基础 文件I/O与网络I/O 概述
面试官:请问计算机如果处理二进制加减运算 并说说补码是什么
知识点: Java ReentrantReadWriteLock 读写锁共享锁与排他锁
知识点: Java公平锁与非公平锁 原理讲解ReentrantLock 锁的饥饿效应及解决办法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值