Nio详解

2023年 10月 11日 136.0k 0

nio

1.概念

NIO代表的一个词汇叫着IO多路复用。它是由操作系统提供的系统调用,早期这个操作系统调用的名字是select,但是性能低下,后来渐渐演化成了Linux下的epoll和Mac里的kqueue。我们一般就说是epoll,因为没有人拿苹果电脑作为服务器使用对外提供服务。而Netty就是基于Java NIO技术封装的一套框架

为什么要封装,因为原生的Java NIO使用起来没那么方便,而且还有臭名昭著的bug,Netty把它封装之后,提供了一个易于操作的使用模式和接口,用户使用起来也就便捷多了。

2.BIO与NIO

2.1BIO

说NIO之前先说一下BIO(Blocking IO),如何理解这个Blocking呢?

  • 客户端监听(Listen)时,Accept是阻塞的,只有新连接来了,Accept才会返回,主线程才能继
  • 读写socket时,Read是阻塞的,只有请求消息来了,Read才能返回,子线程才能继续处理
  • 读写socket时,Write是阻塞的,只有客户端把消息收了,Write才能返回,子线程才能继续读取下一个请求
  • 传统的BIO模式下,从头到尾的所有线程都是阻塞的,这些线程就干等着,占用系统的资源,什么事也不干。

    2.2 NIO

    那么NIO是怎么做到非阻塞的呢。它用的是事件机制。它可以用一个线程把Accept,读写操作,请求处理的逻辑全干了。如果什么事都没得做,它也不会死循环,它会将线程休眠起来,直到下一个事件来了再继续干活,这样的一个线程称之为NIO线程。用伪代码表示:

    while true {
        events = takeEvents(fds)  // 获取事件,如果没有事件,线程就休眠
        for event in events {
            if event.isAcceptable {
                doAccept() // 新链接来了
            } elif event.isReadable {
                request = doRead() // 读消息
                if request.isComplete() {
                    doProcess()
                }
            } elif event.isWriteable {
                doWrite()  // 写消息
            }
        }
    }
    

    2.3NIO与IO的区别

    IO NIO
    面向流(Stream Oriented) 面向缓冲区
    阻塞IO 非阻塞IO
    选择器Selector

    3.NIO详解

    3.1 组成

    Buffer:

    • 缓存数组,就是一个内存块,底层用数组实现
    • 与Channel进行数据的读写。
    • 数据的读取写入是通过Buffer, 这个和BIO 一样, 而BIO 中要么是输入流,或者是输出流, 不能双向,但是NIO的Buffer 是可以读也可以写, 需要 flip 方法切换。

    Channel:

    • 通信通道,每个客户端连接都会建立一个Channel通道
    • 我的理解是:客户端直接与Channel进行通信,当客户端发送消息时,消息就流通到Channel里面,本地程序需要将Channel里面的数据存放在Buffer里面,才可以查看;当本地需要发送消息时,先把消息存在Buffer里面,再将Buffer里面的数据放入Channel,数据就流通到了客户端
    • 总而言之:Buffer就是本地程序与Channel数据交换的一个中间媒介

    SelectionKey、Selector:

    • NIO之所以是非阻塞的,关键在于它一个线程可以同时处理多个客户端的通信。而Selector就是它一个线程如何处理多个客户端通信的关键,一个Selector就对应一个线程
    • 首先在创建与客户端连接的Channel时,应该调用 Channel.register()方法,将Channel注册到一个Selector上面。调用该方法后,会返回一个SelectionKey对象,该对象与Channel是一一对应的。而Selector则通过管理SelectionKey的集合间接的去管理各个Channel。示例图如下:

    • Selector具体如何管理这么多个通信的呢?这就引出了事件

    事件、以及NIO的工作流程介绍

    • **事件:**当将Channel绑定到Selector上面时,必须同时为该Channel声明一个监听该Channel的事件(由Channel和该Channel的事件一起组成了SelectionKey),并将SelectionKey加入到Selector的Set集合中去
    • 当有客户端建立连接或者进行通信,会在对应的各个Channel中产生不同的事件。
    • Selector会一直监听所有的事件,当他监听到某个SelectionKey中有事件产生时,会将所有产生事件的SelectionKey统一加入到一个集合中去
    • 而我们则需要获取到这个集合,首先对集合中的各个SelectionKey进行判断,判断它产生的是什么事件,再根据不同的事件进行不同的处理。
    • 在操作这个SelectionKey集合的时候,其实我们就是在一个线程里面对几个不同客户端的连接进行操作。具体的关系图如下:

    3.2缓冲区(Buffer)

    基本介绍

    缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由Buffer。

    Buffer类介绍

    • 基类是Buffer抽象类
    • 基类派生出基于基本数据类型的7个xxxBuffer 抽象类,没有boolean相关的buffer类。
    • 除了ByteBuffer外,每个基本数据的抽象类 xxxBuffer 类下面都派生出转向 ByteBuffer 的类 ByteBufferXxxAsBufferL 和 ByteBufferAsXxxBufferB实现类;以及 DirectXxxBufferU 和 DirectXxxBufferS 和 HeapXxxBuffer==(具体实例对象类)==这五个类。
    • 就只有抽象类CharBuffer 派生出了第六个类StringCharBuffer。
    • ByteBuffer只派生出了 HeapByteBuffer 和 MappedByteBufferR 两个类

    Buffer类主要属性

    属性 描述
    Capacity 容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变
    Limit 表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的
    Position 位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变改值,为下次读写作准备
    Mark 标记 ,一般不会主动修改,在flip()被调用后,mark就作废了。

    mark

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论