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();
        }
    }
}

客户端:

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.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.4.2 Telnet 方式测试

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

解决方案:

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

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

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

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

1.5 总结

1.5.1 BIO缺点:

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

1.5.2 使用场景:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌晨里的无聊人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值