zoukankan      html  css  js  c++  java
  • 从实践模拟角度再议bio nio【重点】

    从实践角度重新理解BIO和NIO

    https://mp.weixin.qq.com/s/rsvAmmoJiseEmjChI95m6Q

    1 bio的2次阻塞与缺陷

    服务器端在启动后,首先需要等待客户端的连接请求(第一次阻塞),如果没有客户端连接,服务端将一直阻塞等待,然后当客户端连接后,服务器会等待客户端发送数据(第二次阻塞),如果客户端没有发送数据,那么服务端将会一直阻塞等待客户端发送数据。

    BIO会产生两次阻塞,第一次在等待连接时阻塞,第二次在等待数据时阻塞。

    当我们的服务器接收到一个连接后,并且没有接收到客户端发送的数据时,是会阻塞在read()方法中的,那么此时如果再来一个客户端的请求,服务端是无法进行响应的。在不考虑多线程的情况下,BIO是无法处理多个客户端请求的

    2 多线程的bio

    我们只需要在每一个连接请求到来时,创建一个线程去执行这个连接请求,就可以在BIO中处理多个客户端请求了,这也就是为什么BIO的其中一条概念是服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理

      while (true) {
                    System.out.println();
                    System.out.println("服务器正在等待连接...");
                    Socket socket = serverSocket.accept(); 【重点阻塞】
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            System.out.println("服务器已接收到连接请求...");
                            System.out.println();
                            System.out.println("服务器正在等待数据...");
                            try {
                                socket.getInputStream().read(buffer); 【重点阻塞】
                            } catch (IOException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                            System.out.println("服务器已经接收到数据");
                            System.out.println();
                            String content = new String(buffer);
                            System.out.println("接收到的数据:" + content);
                        }
                    }).start();
    
                }
    

    3 多线程bio弊端

    1)线程切换

    2)线程资源浪费在不说话的连接上

    4 模拟nio,同步非阻塞模型

    其实NIO需要解决的最根本的问题就是存在于BIO中的两个阻塞,分别是等待连接时的阻塞和等待数据时的阻塞。

    如果单线程服务器在等待数据时阻塞,那么第二个连接请求到来时,服务器是无法响应的。如果是多线程服务器,那么又会有为大量空闲请求产生新线程从而造成线程占用系统资源,线程浪费的情况。

    单线程服务器接收数据时阻塞,而无法接收新请求的问题,那么其实可以让服务器在等待数据时不进入阻塞状态,问题不就迎刃而解了吗?

    4.1

    //设置为非阻塞
                serverSocketChannel.configureBlocking(false);【重点非阻塞】
                while(true) {
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    if(socketChannel==null) {
                        //表示没人连接
                        System.out.println("正在等待客户端请求连接...");
                        Thread.sleep(5000);
                    }else {
                        System.out.println("当前接收到客户端请求连接...");
                    }
                    if(socketChannel!=null) {
                        //设置为非阻塞
                        socketChannel.configureBlocking(false); 【重点非阻塞】
                        byteBuffer.flip();//切换模式  写-->读
                        int effective = socketChannel.read(byteBuffer);
                        if(effective!=0) {
                            String content = Charset.forName("utf-8").decode(byteBuffer).toString();
                            System.out.println(content);
                        }else {
                            System.out.println("当前未收到客户端消息");
                        }
                    }
    

    在这种解决方案下,虽然在接收客户端消息时不会阻塞,但是又开始重新接收服务器请求,用户根本来不及输入消息,服务器就转向接收别的客户端请求了

    4.2

    我们将连接存储在一个list集合中,每次等待客户端消息时都去轮询,看看消息是否准备好,如果准备好则直接打印消息。

      //设置为非阻塞
                serverSocketChannel.configureBlocking(false);
                while(true) {
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    if(socketChannel==null) {
                        //表示没人连接
                        System.out.println("正在等待客户端请求连接...");
                        Thread.sleep(5000);
                    }else {
                        System.out.println("当前接收到客户端请求连接...");
                        socketList.add(socketChannel);  【重点缓存】
                    }
                    for(SocketChannel socket:socketList) {  【重点遍历缓存】
                        socket.configureBlocking(false);
                        int effective = socket.read(byteBuffer);
                        if(effective!=0) {
                            byteBuffer.flip();//切换模式  写-->读
                            String content = Charset.forName("UTF-8").decode(byteBuffer).toString();
                            System.out.println("接收到消息:"+content);
                            byteBuffer.clear();
                        }else {
                            System.out.println("当前未收到客户端消息");
                        }
                    }
    

    我们采用了一个轮询的方式来接收消息,每次都轮询所有的连接,看消息是否准备好,测试用例中只是三个连接,所以看不出什么问题来,但是我们假设有1000万连接,甚至更多,采用这种轮询的方式效率是极低的。

    另外,1000万连接中,我们可能只会有100万会有消息,剩下的900万并不会发送任何消息,那么这些连接程序依旧要每次都去轮询,这显然是不合适的。

    5 真实nio

    在真实NIO中,并不会在Java层上来进行一个轮询,而是将轮询的这个步骤交给我们的操作系统来进行,他将轮询的那部分代码改为操作系统级别的系统调用(select函数,在linux环境中为epoll),在操作系统级别上调用select函数,主动地去感知有数据的socket

    我们写的Java程序其本质在轮询每个Socket的时候也需要去调用系统函数,那么轮询一次调用一次,会造成不必要的上下文切换开销。用户态-内核态切换

    5.1 windows select

    如果select没有查询到到有数据的请求,那么将会一直阻塞(是的,select是一个阻塞函数)。如果有一个或者多个请求已经准备好数据了,那么select将会先将有数据的文件描述符置位,然后select返回。返回后通过遍历查看哪个请求有数据。

    • 底层存储依赖bitmap,处理的请求是有上限的,为1024

    • fd 用户-内核拷贝
    • 再次遍历

    5.2 linux poll

    poll内部存储不依赖bitmap,而是使用pollfd数组的这样一个数据结构,数组的大小肯定是大于1024的。但仍有后2个缺点

    5.3 epoll

    epoll和上述两个函数最大的不同是,它的fd是共享在用户态和内核态之间的,所以可以不必进行从用户态到内核态的一个拷贝,这样可以节约系统资源;另外,在select和poll中,如果某个请求的数据已经准备好,它们会将所有的请求都返回,供程序去遍历查看哪个请求存在数据,但是epoll只会返回存在数据的请求,这是因为epoll在发现某个请求存在数据时,首先会进行一个重排操作,将所有有数据的fd放到最前面的位置,然后返回(返回值为存在数据请求的个数N),那么我们的上层程序就可以不必将所有请求都轮询,而是直接遍历epoll返回的前N个请求,这些请求都是有数据的请求。

    6

    • Java NIO (non-blocking I/O): 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理

     阻塞io个人认为可再分为单线程和多线程,缺点:

      single:连接1的read会阻塞连接2的connect

      multi:线程切换、僵尸连接

    本质上io都是阻塞的,multi版本业务上非阻塞

    io复用之非阻塞体现在用户态select/epoll阻塞,内核态非阻塞轮训各channel,which在连接创建时被注册到selector上

    另一片比较重要的文章:5种io模型摘要

  • 相关阅读:
    DW吃瓜课程——机器学习理论知识笔记(四)
    DW吃瓜课程——机器学习理论知识笔记(三)
    DW吃瓜课程——机器学习理论知识笔记(二)
    DW吃瓜课程——机器学习理论知识笔记(一)
    DataWhale编程实践——区块链学习笔记
    CV入门系列笔记——全球人工智能技术创新大赛【热身赛】CV异常检测赛道
    强化学习入门笔记系列——DDPG算法
    强化学习入门笔记系列——稀疏奖赏和模仿学习
    强化学习入门笔记系列——DQN算法
    Java_SPI思想
  • 原文地址:https://www.cnblogs.com/silyvin/p/12150953.html
Copyright © 2011-2022 走看看