5

原来传统BIO的局限性在这里!

 3 years ago
source link: http://www.justdojava.com/2021/06/16/BIO/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

原来传统BIO的局限性在这里!

发表于 2021-06-16

| 分类于 Java

大家都知道传统的 BIO 网络模型有各种各样的缺点,于是就有了关于 NIO 网络模型的出现,更多的人也都开始喜欢使用 Netty 这种框架来进行开发,而摒弃了传统的 BIO 的模型,今天阿粉就给大家说一下为什么这么多人对 BIO 网络模型这么的头疼。

BIO 网络模型实际上就是使用传统的 Java IO 编程,相关联的类和接口都在 java.io 下。

BIO 模型到底是个什么玩意?

BIO (blocking I/O) 同步阻塞,我们看这个翻译,blocking I/O,实际上就能看出来,就是阻塞,当我们的客户端发起请求的时候,服务端就会开启一个线程,专门为这个客户端提供对应的读写操作,只要客户端发起了,这个服务端的线程就一直保持存在,就算客户端啥也不干,那也在那里开着,就是玩。

不过说一句,现在用 BIO 的不是很多了,因为强制使用的时代过去了,现在还有用 JDK1.4 以下的项目么?估计应该是没有了,阿粉之前曾经维护过一个使用 JRUN 的项目,使用的JDK的版本,就是非常古老的。

我们来整一个服务端和客户端来看看是什么样子的模型。

Server端:

public class TimeServer {
    public static void main(String[] args){
        ServerSocket server=null;
        try {
            server=new ServerSocket(18080);
            System.out.println("服务启动 端口:18080...");
            while (true){
                Socket client = server.accept();
                //每次接收到一个新的客户端连接,启动一个新的线程来处理
                new Thread(new TimeServerHandler(client)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                server.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

public class TimeServerHandler implements Runnable{
    private Socket clientProxxy;

    public TimeServerHandler(Socket clientProxxy) {
        this.clientProxxy = clientProxxy;
    }

    @Override
    public void run() {
        BufferedReader reader = null;
        PrintWriter writer = null;
        try {
            reader = new BufferedReader(new InputStreamReader(clientProxxy.getInputStream()));
            writer =new PrintWriter(clientProxxy.getOutputStream()) ;
            while (true) {//因为一个client可以发送多次请求,这里的每一次循环,相当于接收处理一次请求
                String request = reader.readLine();
                if (!"GET CURRENT TIME".equals(request)) {
                    writer.println("BAD_REQUEST");
                } else {
                    writer.println(Calendar.getInstance().getTime().toLocaleString());
                }
                writer.flush();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                writer.close();
                reader.close();
                clientProxxy.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Client端:

public class TimeClient {
    public static void main(String[] args)  {
        BufferedReader reader = null;
        PrintWriter writer = null;
        Socket client=null;
        try {
            client=new Socket("127.0.0.1",18080);
            writer = new PrintWriter(client.getOutputStream());
            reader = new BufferedReader(new InputStreamReader(client.getInputStream()));

            while (true){//每隔5秒发送一次请求
                writer.println("GET CURRENT TIME");
                writer.flush();
                String response = reader.readLine();
                System.out.println("Current Time:"+response);
                Thread.sleep(5000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                writer.close();
                reader.close();
                client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

执行结果如下:


服务端:
服务启动 端口:18080...

客户端:
Current Time:2021-6-16 11:37:34
Current Time:2021-6-16 11:37:39
Current Time:2021-6-16 11:37:44
Current Time:2021-6-16 11:37:49
Current Time:2021-6-16 11:37:54
Current Time:2021-6-16 11:37:59
Current Time:2021-6-16 11:38:04

我们使用的是 Client 发送请求指令”GET CURRENT TIME”给server端,每隔5秒钟发送一次,每次 Server 端都返回当前时间。

这就相当于是多个 Client 同时请求 Server ,每个 Client 创建一个线程来进行处理.

1.jpg

Accpetor thread 只负责与 Client 建立连接,worker thread用于处理每个thread真正要执行的操作。

在到了这里的时候,我们就发现了一些事情,感觉不对了有没有,

BIO 的局限性一

  • 每一个 Client 建立连接后都需要创建独立的线程与 Server 进行数据的读写,业务处理。

这时候就会出现什么样子的问题,我们如果说有上千个客户端的时候,我们服务端就会创建上千个线程,有多少客户端,就创建多少个线程,这对于 Java 来说, 代价实在是太大。

如果说我们线程在到达一定数量的时候,我们在做线程的切换的时候,大家可以想象一下对资源的浪费是什么样子的,一个线程和一百个线程甚至超过一千个线程的时候,在线程进行上下文切换的时候,会出现什么样子的问题。

针对某个线程来说,这种 BIO 的模型,在这个线程读取数据的时候,如果没有数据了,那线程就开始了阻塞,为了能够做到响应数据,这个线程在一直阻塞,这时候有新的请求的时候,线程阻塞,好吧,那我只能等,一直处于一个等待不能执行的状态。

BIO 的局限性二

  • 并发数大的时候,会创建非常多的线程来处理连接,系统资源会出现非常大的开销。

BIO 的局限性三

  • 在线程阻塞的时候,会造成资源的浪费。

实际上说是三个局限性,总得来说,他就是一个局限,浪费资源,开销大,这是非常致命的,一个小小的功能的话,你占用的服务器大量的资源,只是硬件上这块的内容,都得增加多少的成本,现在万恶的资本家们,抱着能省就省的原则,又扯远了,我们还是回归到 BIO 上。

而且这种 BIO 的模型,在本质上说,实际上就相当于是一个 1:1 的关系,而这种 1:1 的关系如果在客户端非常多的时候,创建的线程数所浪费的资源是非常巨大的,所以就出现了另外一种模型,NIO 模型。而 NIO 模型实际上目前使用的那可真的是太普遍了,比如说 Netty ,都是选择使用这种模型,不再继续使用 BIO 的模型了。

而关于 NIO 阿粉已经说了很多次,文章都写了好多,阿粉把文章都给大家放在下面,按照顺序阅读,不然阿粉怕大家会有点混乱

什么是BIO,NIO?他们和多路复用器有啥关系?

Java:前程似锦的 NIO 2.0

用Socket编程?我还是选择了Netty

而据说 JDK1.8 之后的 原生NIO API 中,会有空轮询的bug,会让服务器的 CPU 瞬间飙升到100%,具体真假,阿粉反正是没有碰到过,也没有测试出来,真的假的就留给大家去试试了。

《田守枝的Java技术博客》 《Netty权威指南》


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK