• 【Netty】nio阻塞&非阻塞&Selector


    阻塞VS非阻塞

    阻塞
    • 阻塞模式下,相关方法都会导致线程暂停。

      • ServerSocketChannel.accept() 会在没有建立连接的时候让线程暂停

      • SocketChannel.read()会在没有数据的时候让线程暂停。

      • 阻塞的表现就是线程暂停了,暂停期间不会占用CPU,但线程相当于闲置。

    • 单线程下,阻塞方法之间相互影响,几乎不能正常工作,需要多线程支持。

    • 但多线程下又有新问题。

      • 32 位 jvm 一个线程 320k,64 位 jvm 一个线程 1024k,如果连接数过多,必然导致 OOM,并且线程太多,反而会因为频繁上下文切换导致性能降低

      • 可以采用线程池技术来减少线程数和线程上下文切换,但治标不治本,如果有很多连接建立,但长时间 inactive,会阻塞线程池中所有线程,因此不适合长连接,只适合短连接

    服务器端

    这个代码只能每次在连接到时候读取一次连接事件,进行遍历,其余事件阻塞,即使有其他的读写事件也没有反回应。

    1. public class TestSocketChannel {
    2.   public static void main(String[] args) throws IOException {
    3.       //开启服务
    4.       ServerSocketChannel ssc = ServerSocketChannel.open();
    5.       //ban
    6.       ssc.bind(new InetSocketAddress(8080));
    7.       List channels = new ArrayList<>();
    8.       while (true) {
    9.           //等待建立连接
    10.           SocketChannel sc = ssc.accept();
    11.           channels.add(sc);
    12.           Iterator iterator = channels.iterator();
    13.           while (iterator.hasNext()) {
    14.               SocketChannel channel = iterator.next();
    15.               ByteBuffer buffer = ByteBuffer.allocate(10);
    16.               //读取数据
    17.               int len = channel.read(buffer);
    18.               ByteBufferUtil.debugAll(buffer);
    19. buffer.clear();
    20.       log.debug("after read...{}", channel);
    21.           }
    22.       }
    23.   }
    24. }

    客户端

    1. public class SocketChannelClient {
    2.   public static void main(String[] args) throws IOException {
    3.       SocketChannel sc = SocketChannel.open();
    4.       sc.connect(new InetSocketAddress("localhost", 8080));
    5.       System.out.println("waiting...");
    6.   }
    7. }

    非阻塞
    • 非阻塞模式下,相关的方法都不会让线程暂停

      • 在ServerSocketChannel.accept()在没有建立连接时,会返回null.

      • SocketChannel.read在没有数据可读时返回0,但线程不必阻塞,可以执行其他SocketChannel的read或者ServerSocketChannel.accept

      • 写数据的时候,知识等待数据写入channel即可,无需等Channel通过网络把数据发出去。

    • 非阻塞模式下,即使没有连接建立和可读数据,线程任然在不断运行,拜拜浪费CPU

    • 数据复制过程中,线程实际还是阻塞的。(AIO改进的地方)

    服务端代码

    1. package com.aqiuo.socketchannel;
    2. import com.aqiuo.buffer.ByteBufferUtil;
    3. import lombok.extern.slf4j.Slf4j;
    4. import java.io.IOException;
    5. import java.net.InetSocketAddress;
    6. import java.nio.ByteBuffer;
    7. import java.nio.channels.ServerSocketChannel;
    8. import java.nio.channels.SocketChannel;
    9. import java.util.ArrayList;
    10. import java.util.Iterator;
    11. import java.util.List;
    12. @Slf4j
    13. public class TestSocketChannel {
    14.    public static void main(String[] args) throws IOException {
    15.        //开启服务
    16.        ServerSocketChannel ssc = ServerSocketChannel.open();
    17.        //绑定端口
    18.        ssc.bind(new InetSocketAddress(8080));
    19.        ssc.configureBlocking(false); //非阻塞模式
    20.        //连接的集合
    21.        List channels = new ArrayList<>();
    22.        while (true) {
    23.            //等待建立连接非阻塞,线程还好继续向下运行,没有连接返回null
    24.            SocketChannel sc = ssc.accept();
    25.            if(sc!=null){
    26.                log.info("connected...{}",sc);
    27.                sc.configureBlocking(false);//非阻塞模式
    28.                channels.add(sc);
    29.           }
    30.            Iterator iterator = channels.iterator();
    31.            while (iterator.hasNext()) {
    32.                SocketChannel channel = iterator.next();
    33.                ByteBuffer buffer = ByteBuffer.allocate(10);
    34.                //非阻塞读取数据
    35.                //接受客户端发送的数据。没有读到数据返回0
    36.                int len = channel.read(buffer);
    37.                if(len>0){
    38.                    buffer.flip();
    39.                    ByteBufferUtil.debugAll(buffer);
    40.                    buffer.clear();
    41.                    log.info("after read...{}",channel);
    42.               }
    43.           }
    44.       }
    45.   }
    46. }
     
    
    多路复用

    单线程可以搭配Selector完成对多个channel可读可写事件的监控,称之为多路复用

    • 多路复用仅仅针对网络IO,普通文件IO无法利用多路复用。

    • 如果不用Selector的非阻塞模式,线程大部分事件都在做无用功,而Selector能够保证。

      • 有连接事件时采去连接

      • 有可读事件才去读取。

      • 有可写事件才去写入。

        • 限于网络传输能力,Channel 未必时时可写,一旦 Channel 可写,会触发 Selector 的可写事件

    Selector

    创建
    Selector selector = Selector.open();
    绑定 Channel 事件

    也称之为注册事件,绑定的事件 selector 才会关心

    1. channel.configureBlocking(false);
    2. SelectionKey key = channel.register(selector, 绑定事件);
    • channel 必须工作在非阻塞模式

    • FileChannel 没有非阻塞模式,因此不能配合 selector 一起使用

    • 绑定的事件类型可以有

      • connect - 客户端连接成功时触发

      • accept - 服务器端成功接受连接时触发

      • read - 数据可读入时触发,有因为接收能力弱,数据暂不能读入的情况

      • write - 数据可写出时触发,有因为发送能力弱,数据暂不能写出的情况

    监听 Channel 事件

    可以通过下面三种方法来监听是否有事件发生,方法的返回值代表有多少 channel 发生了事件

    方法1,阻塞直到绑定事件发生

    int count = selector.select();

    方法2,阻塞直到绑定事件发生,或是超时(时间单位为 ms)

    int count = selector.select(long timeout);

    方法3,不会阻塞,也就是不管有没有事件,立刻返回,自己根据返回值检查是否有事件

    int count = selector.selectNow();

    💡 select 何时不阻塞
    • 事件发生时

      • 客户端发起连接请求,会触发 accept 事件

      • 客户端发送数据过来,客户端正常、异常关闭时,都会触发 read 事件,另外如果发送的数据大于 buffer 缓冲区,会触发多次读取事件

      • channel 可写,会触发 write 事件

      • 在 linux 下 nio bug 发生时

    • 调用 selector.wakeup()

    • 调用 selector.close()

    • selector 所在线程 interrupt

  • 相关阅读:
    2.20日学习打卡----初学Vue3
    交互式shell和非交互式shell、登录shell和非登录shell
    程序员如何过中秋 | 如何画月饼
    小黑腰酸背痛继续leetcode:剑指 Offer II 027. 回文链表
    【Cherno的OpenGL视频】Creating tests in OpenGL
    上半年Java岗面试总结:Java+并发+Spring+MySQL+分布式+Redis+算法+JVM等
    C++行为型模式-中介者模式
    计算机毕业设计(附源码)python中医保健系统
    【linux】——程序地址空间_终
    Neural Ordinary Differential Equations(NIPS2018)
  • 原文地址:https://blog.csdn.net/m0_62645012/article/details/139746244