• IO系列第一章——BIO


    1、BIO

    关于BIO有同学可能会说不知道,但是知道IO。其实你之前学过那些java.io包下面的输入流和输出流就是BIO中的文件IO部分。例如InputStream、OutputStream。还有一部分就是网络IO,在java.net包下面的提供了部分网络API,例如Socket, ServerSocket。

    1.1 概念

    Java BIO就是传统的java io编程,在java.io包下有相关的类和接口。
    BIO:同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理。如果这个连接不做任何事情就会造成不必要的线程开销(当然可以通过线程池机制改善)。

    同步和异步是针对应用程序和内核的交互而言的。
    同步:指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪。

    生活案例:自己拿上银行卡去银行取钱(使用同步IO时,Java自己处理IO读写)。

    异步:指的是用户进程触发IO操作以后便开始做其他的事情,而当IO操作已经完成的时候会得到IO完成的通知。

    生活案例:托朋友拿着自己的银行卡去银行取钱,告诉朋友自己的银行卡密码,自己去办理其他事情。同时,你还要告诉朋友取完钱给你送到哪里。(
    使用异步I/O时, Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS )。

    阻塞和非阻塞是针对进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式。
    阻塞:指的是准备对文件进行读写时,如果当时没有东西可读,或暂时不可写,程序就进入等待状态,直到有东西可读或可写为止。

    生活案例:你在ATM取款,发现前面有人,你只能等待,等其他人办理完你才能取钱(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回)。

    非阻塞:指的是如果没有东西可读,或不可写,读写函数马上返回,而不会等待。

    生活案例:在银行办业务时,人多要先取个号,然后我们可以跟朋友开黑,等轮到我们,银行就会通知,这时候我们就可以去办业务了(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)。

    1.2 工作模型

    在这里插入图片描述
    1、启动一个服务器(serverSocket),等待客户端的连接
    2、启动一个客户端(Socket),然后与服务器进行通信(默认情况下服务器端需要给每个客户端建立一个线程与其通信)。
    3、客户端发出请求,询问服务器是否有线程响应,如果没有就会等待或被拒绝。
    4、如果有响应,客户端线程会等待请求结束后再继续执行。

    1.3 案例

    使用BIO模型编写一个服务器(服务器端口为10086),客户端可以发消息给服务器。
    服务器端:

    package testIO;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    /**
     * BIO模式下的服务器
     */
    public class BIOServer {
        public static void main(String[] args) {
            try {
    //服务器
                ServerSocket serverSocket=new ServerSocket(10086);
                System.out.println("服务器启动--端口号是10086");
                while(true){
    //连接到服务器的客户端--堵塞方法
                    Socket client = serverSocket.accept();
                    System.out.println("有客户端连接成功!");
    //为每个客户端连接都创建一个新的线程与之通信
                    new Thread(()->{
                        System.out.print("线程id:"+Thread.currentThread().getId());
                        System.out.println("线程名 称:"+Thread.currentThread().getName());
                        try {
                            int len=0;
                            byte[] byteArr=new byte[1024];
    //读取来自客户端的数据
                            InputStream inputStream = client.getInputStream();
                            while((len=inputStream.read(byteArr))!=-1){
                                System.out.println(len);
                                String msg=new String(byteArr,0,len);
                                System.out.println("来自客户端的消息:"+msg);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }).start();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    客户端:

    package testIO;
    
    import java.io.IOException;
    import java.io.OutputStream;
    import java.net.Socket;
    /**
         * BIO模式的客户端
         */
        public class BIOClient {
            public static void main(String[] args) {
                try {
                    //创建客户端
                    Socket client=new Socket("127.0.0.1", 10086);
                    String msg="hi,xiaoli";
                    OutputStream outputStream = client.getOutputStream();
                    System.out.println(msg.length());
                    outputStream.write(msg.getBytes(), 0, msg.length());
                    outputStream.close();
                    System.in.read();//目的是让客户端保持与服务器的连接
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述

    1.4 服务器优化+Telnet测试

    1.4.1 通过线程池

    服务端代码改进如下:

    package testIO;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * BIO模式下的服务器
     */
    public class BIOServer {
        public static void main(String[] args) {
            //创建线程池
            ExecutorService executorService= Executors.newCachedThreadPool();
            try {
    //服务器
                ServerSocket serverSocket=new ServerSocket(10086);
                System.out.println("服务器启动--端口号是10086");
                while(true){
    //连接到服务器的客户端--堵塞方法
                    Socket client = serverSocket.accept();
                    System.out.println("有客户端连接成功!");
    //为每个客户端连接都创建一个新的线程与之通信
                    executorService.execute(()->{
                        System.out.print("线程id:"+Thread.currentThread().getId());
                        System.out.println("线程名 称:"+Thread.currentThread().getName());
                        try {
                            int len=0;
                            byte[] byteArrs=new byte[1024];
    //读取来自客户端的数据
                            InputStream inputStream = client.getInputStream();
                            while((len=inputStream.read(byteArrs))!=-1){
    
                                String msg=new String(byteArrs,0,len);
    
                                System.out.println("来自客户端的消息:"+msg);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    });
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    可以看到一直用的是线程池的一个线程
    在这里插入图片描述
    客户端代码还是如上;

    1.4.2 Telnet 方式测试

    这个方法就不需要用到client代码了
    在这里插入图片描述

    解决方案:

    windows7 在这里找
    在这里插入图片描述

    Windows11在这里找;
    在这里插入图片描述

    如果是第⼀次打开这个功能,加载⽐较慢。加载完成后,找到
    Telnet客户端选项,勾选这个选项,然后点击确定保存。
    在这里插入图片描述
    在这里插入图片描述
    OK了!!

    这回可以进来了
    在这里插入图片描述
    按Ctrl + ]
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    可以看到已经发送成功了。

    1.5 总结

    1.5.1 BIO缺点:

    客户端越来越多,服务器就要开启越来越多的线程,对服务器的压力就会越大;而且客户端发起一个连接之后不一定都在做事情,这个时候服务器也要维护,造成不必要的压力。

    1.5.2 使用场景:

    BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,该方式是JDK1.4以前的唯一选择,但程序直观简单易理解。

  • 相关阅读:
    Win7/10/11文件拖拽导致资源管理器闪退问题
    深入理解 Java 中的 synchronized 关键字
    可能是2022最详细的结构体讲解
    ES和kibana安装
    Deno 的配置文件、框架,标准库
    docker系列(9) - docker-compose
    总结单例模式的写法
    Linux入门
    Linux定时任务切割日志
    12月03日(第五天)
  • 原文地址:https://blog.csdn.net/delete_bug/article/details/126493947