• Java BIO 编程


    Java BIO 基本介绍

    1. Java BIO 就是传统的java io 编程,其相关的类和接口在 java.io
    2. BIO(blocking I/O) : 同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器)。
    3. BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,程序简单易理解

    Java BIO 工作机制

    ​ 采用 BIO 通信模型 的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接。我们一般通过在while(true) 循环中服务端会调用 ServerSocket的accept() 方法等待接收客户端的连接的方式监听请求,一旦接收到一个连接请求,就可以建立通信套接字Socket,并为这个通信套接字创建一个新的客户端线程处理这条 Socket 链路。如下图所示。

    img

    ​ 但是在池化技术大行其道的今天,线程的创建和销毁时非常浪费资源的,使用线程池将线程利用起来重复使用,线程池可以设置等待队列的大小最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。那么上面的传统 BIO 工作机制图就变成了如下图所示的演进。

    img

    说了这么多很多刚入门的小伙伴依旧很懵逼,看不懂。下面我举一个生活中的例子:

    传统的 BIO 模型工作机制 -:在生活中饭店一般都有大门口接待服务员(类似于 Acceptor 线程 )接待所有进入饭店的客人,当有一个客人进来吃饭后,饭店就招聘一个服务员(类似于创建线程)专门接待这桌客人,这个服务员在此期间不能做其他事情,当客人走后饭店就把这个服务员辞退(类似于销毁线程),有点类似于同生共死,当然世界上没有那家饭店愿意做这种事情,太不现实;

    线程池技术优化后的 BIO 模型工作机制:饭店都会有固定的多个服务员,当有一个客人进来吃饭后,就指定某个服务员专门接待这桌客人(类似于从线程池中取出一个线程),服务员在此期间也不能做其他时间,但是不同的是,当客人走后就可以让这个服务员休息一会(类似于将线程放回线程池等待下次使用),等待下一桌客人的到来。

    Java BIO 应用实例

    1、代码实现思路

    1. 使用线程池替代单个线程,每建立一个客户端连接就从线程池中取出一个线程
    2. serverSocket的accept函数监听客户端连接,如果没有客户端连接,程序将阻塞在accept函数上
    3. 创建handler函数,处理Socket链路数据,接收来自客户端发送的请求,并打印在控制台

    2、服务端代码实现

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @Description: 基于BIO通信模型的TimeServer
     * @Author: hh
     * @date 2020/10/14
     */
    public class TimeServer {
    
        //1、创建一个线程池,如果有客户端连接就创建一个线程
        private static final ExecutorService threadPool = new ThreadPoolExecutor(
                10,
                10,
                120L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100));
    
        public static void main(String[] args) {
            //1、指定监听端口
            int port = 9999;
            try (ServerSocket serverSocket = new ServerSocket(port);) {
                System.out.println("服务端启动,监听端口: " + port);
                while (true) {
                    //2、创建一个无限循环监听客户端连接,如果没有客户端接入,则主线程将阻塞在 accept() 函数上
                    System.out.println("等待连接。。。");
                    Socket socket = serverSocket.accept();
                    System.out.println("连接到一个客户端,主机地址:" + socket.getInetAddress().getHostAddress());
                    threadPool.execute(() -> {
                        handler(socket);
                    });
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * Socket链路处理器
         * @param socket
         */
        private static void handler(Socket socket) {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
                System.out.println(Thread.currentThread().getName() + " - 接收来自客户端信息");
                String body = null;
                //3、循环读取客户端发送信息
                while (true) {
                    if ((body = reader.readLine()) == null) {
                        break;
                    }
                    //4、输出客户端发送信息
                    System.out.println("服务端接收来自客户端信息:" + body);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    3、客户端发起请求

    1、启动 TimeServer,控制台输出如下:

    image-20201014224117450

    2、如果是win10系统电脑,使用 win+R命令打开运行窗口,输入cmd命令

    image-20201014223920218

    3、点击"确定",进入命令行窗口,输入"telnet 127.0.0.1 9999",TimeServer控制台输出如下

    image-20201014224210311

    4、ctrl + ] 命令回显内容,回车,进入编辑状态,输入任何内容,TimeServer控制台将会输出相应内容

    image-20201014224429341

    BIO 模型问题分析

    • 每个请求都需要创建独立的线程,与对应的客户端进行数据 Read,业务处理,数据 Write 。
    • 当并发数较大时,需要创建大量线程来处理连接,系统资源占用较大。
    • 连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在 Read 操作上,造成线程资源浪费
  • 相关阅读:
    cobbler default system 网络安装时主机的menu上只有一个local选项
    tcpdump使用方法
    cobbler 修改 distro_signatures.json
    wireshark in text mode: tshark
    Mac OSX使用隐藏文件夹
    sql中多条件进行排序的问题
    Linux查看打日志文件
    XML Parser Error on line 39: 必须声明元素类型 "domainObjectRenamingRule"。
    Access denied for user '密码'@'192.18.0.0' (using password: YES)
    springboot之DevTools热部署的简单原理解析
  • 原文地址:https://www.cnblogs.com/dtdx/p/13818005.html
Copyright © 2020-2023  润新知