# io-study **Repository Path**: wlyfree/io-study ## Basic Information - **Project Name**: io-study - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2019-09-27 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # `java io` ## 概念理解 ### 用户空间和内核空间 操作系统将虚拟内存划分为两块:内核空间和用户空间。出于安全考虑,他们是隔离的,即用户程序崩溃不影响内核。 `Kernel space`:内核空间,内核代码运行的地方,当进程运行在内核空间时就称为内核态。可以执行任意命令,调用系统的一切资源。 `User space`:用户空间,用户代码运行的地方,当进程运行在用户空间时就称为用户态。`User space`只能做简单的运算,不能直接调用系统资源,需要通过系统调用(`System call`)的方式调用内核的接口来调用系统资源。 ```java int i = 0; // 用户空间 i++; // 用户空间 file.write(str); // 内核空间 i--; // 用户空间 ``` ### 同步、异步、阻塞、非阻塞 同步和异步针对的用户进程触发`IO`操作后是否需要等待`IO`就绪后才能做其他事。 同步(`Synchronization`):用户进程触发`IO`操作后,需要等待或轮询查看`IO`操作是否就绪,不能做其他事。 异步(`Asynchronous`):用户进程触发`IO`操作后便可以做其他事,当`IO`操作完成后用户进程就会得到相应的通知。 阻塞和非阻塞针对的是线 程针对`IO`读写的处理 阻塞(`Block`):阻塞状态下,若没有东西可以读写,则读写函数进入等待状态,线程挂起且不能做其他事,直到有东西可以读写再返回。 非阻塞(`Non-block`):非阻塞状态下,若没有东西可以读写,读写函数会立即返回,无需等待。 ### 数据准备和复制 ![](images/数据准备和复制.png) `I/O`操作包含两个过程,数据准备和数据复制。 数据准备:将数据从`hardware`拷贝到`kernel space`中,这部分数据拷贝是`DMA`操作,无需`CPU`参与。 数据复制:将数据从`kernel space`中拷贝至`user space`中,需要`CPU`参与。 ### 多路复用`IO` > 多路复用适用于高并发的场景,目前主流的多路复用:`select`、`poll`、`epoll`、`kqueue` `select`:底层基于数组,`fd`有容量限制,默认为系统配置1024,需要轮询可读写的`fd`,效率较低,需要将`fd`集合从用户空间拷贝至内核空间。 `poll`:底层基于链表,`fd`无容量限制,其他与`select`基本一致。 `epoll`:底层基于红黑树,`fd`无容量限制,无需将`fd`集合从用户空间拷贝到内核空间;基于`callback`,无需遍历所有文件描述符。 `kqueue`:同`epoll`,不同`os`有不同的实现`linux epoll`、`freeBSD kqueue`、`windows iocp`。 | IO模型 | 相对性能 | 关键思路 | 操作系统 | JAVA支持 | | -------- | -------- | ------------------ | --------------- | ------------------------------------------------------------ | | `select` | 较高 | `Reactor` | `windows/linux` | `linux kernels 2.4`之前都是`select`,`windows`对同步`IO`的支持都是`select` | | `poll` | 较高 | `Reactor` | `linux` | `linux`下的`java nio`框架,`linux kernels 2.6`内核版本之前使用`poll`支持。 | | `epoll` | 高 | `Reactor/Proactor` | `linux` | `linux kernels 2.6+`支持。另外由于`linux`下没有`windows`下的`IOCP`技术提供真正的异步`IO`支持,所以使用`epoll`模拟异步`IO` | | `kqueue` | 高 | `Proactor` | `linux` | 目前`java`版本不支持 | ### IO模型 #### `BIO(Bloking IO)`:同步阻塞IO ![](images/BIO.png) - 同步:进程发起`IO`操作后被挂起,等待`IO`操作完成后才能继续处理。 - 阻塞:`IO`的两个阶段数据准备和数据复制均为阻塞。 #### `NIO(Non Blocking IO)`:同步非阻塞IO ![](images/NIO.png) - 同步:进程发起`IO`操作后立即返回,数据准备过程不阻塞,用户进程不断主动询问kernel数据准备好了没有。 - 非阻塞:数据准备非阻塞,数据复制阻塞。 #### `IO`多路复用(`IO Multiplexing`) ![](images/IO多路复用.png) `IO`多路复用,也称为事件驱动`IO`,主要实现有`select`、`poll`、`epoll`、`kqueue`。 同`NIO`相比,数据准备过程发生了一个系统调用`select`,进程轮询的过程交由`select`函数实现,但是`select`可以在一个线程中同时处理多个`IO`请求。 进程发起`IO`操作后,发生系统调用`select`,`select`函数会阻塞用户进程,同时会监听多个`fd`,数据准备好了之后,再发起`recvfrom`系统调用。 - 同步:进程发起`IO`操作后,等待通知数据准备就绪,阻塞于系统调用`select`,当数据准备好后,返回可读。然后在发起系统调用`recvfrom`。 - 非阻塞:可以搭配非阻塞`IO`使用,数据准备过程中只有`select`操作是阻塞的,数据复制整个过程都是阻塞的。 `select`:将要监听的读、写以及异常描述符拷贝到内核空间,然后遍历所有描述符,如果有感兴趣的事件发生,那么就返回。 #### 信号驱动`IO` ![](images/信号驱动.png) 同步:进程发起`IO`操作后,注册一个`SIGIO`信号,当数据准备就绪时,交由信号处理。数据准备过程异步,数据复制过程同步。 非阻塞:数据准备过程非阻塞,基于信号处理无需轮询。数据复制过程阻塞。 #### `AIO(Asynchronous IO)`:异步非阻塞IO ![](images/AIO.png) - 异步:整个过程是异步的,进程发起`IO`操作后,就直接返回不予理睬了,直到`kernel`发送一个信号,告诉进程说`IO`完成。 - 非阻塞:数据准备和复制阶段均不会阻塞。 #### 五种`IO`模型对比 ![](images/IO模型对比.png) ## 核心组件 ### `BIO` > `BIO`是面向流的。 ### `NIO` > `NIO`是面向缓冲的。 #### 缓冲区(`Buffer`) ##### 核心属性 缓冲区本质上是一个数组,通过核心属性`mark`、`position`、`limit`、`capacity`来控制缓冲区。 `mark`:标记,记住当前`position`的位置,通过`reset()`回复到标记`mark`的位置。 `position`:指向下一个将要被读取或写入元素的索引,`get()、put()`会自动更新此属性,`position`默认为0。 `limit`:读模式下,指定还有多少数据需要取出;写模式下,指定还有多少空间可以放入。默认=`capacity`。 `capacity`:指定可以存储在缓冲区中的最大容量,底层数组最大容量,初始化时分配,恒定不变。 核心属性满足此规则:`0 <= mark <= position <= limit <= capacity` ##### 核心函数 核心函数:`put()`、`get()` ##### 常用函数 ```java // 从写模式切换到读模式。 public final Buffer flip() { limit = position; position = 0; mark = -1; return this; } // 清理工作,将核心属性恢复至初始化模式,但是不清除数据。 public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; } // 回复标记`mark`的位置,即将`position`指向`mark`。 public final Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException(); position = m; return this; } // 重新写入数据使用。 public final Buffer rewind() { position = 0; mark = -1; return this; } ``` ##### 扩展 子缓冲区:`buffer.slice()`,和父缓冲区共享数据,变更也会体现到父缓冲区中。 只读缓冲区:`buffer.asReadOnlyBuffer()`,顾名思义只读。 直接缓冲区:`buffer.allocateDirect()`,非直接缓冲区将缓冲区建立在`JVM`内存中,直接缓冲区将缓冲区建立在物理内存中,目的是为了加速`IO`。 内存映射(`Memory Map`):`java.nio.channels.FileChannel.map(...)` #### 选择器(`Selector`) `Reactor`工作模式,`I/O`调用不会被阻塞,可以向`Selector`中注册事件,发生事件时系统再通知我们。 工作模式 - 向`Selector`对象注册感兴趣的事件。 - 从`Selector`中获取感兴趣的事件。 - 根据不同的事件进行相应的处理。 #### 通道(`Channel`) 通道是一个对象,负责数据的读取和写入,数据读取或写入并非直接操作`Channel`,而是基于`Buffer`处理。 ##### 读取数据流程 - 通过`FileInputStream`获取`Channel` - 创建`Buffer` - 将数据从`Channel`读取到`Buffer`中 ##### 写入数据流程 - 通过`FileOutPutStream`获取`Channel` - 创建`Buffer` - 将数据从`Channel`写到`Buffer`中 ##### 常用实现类 `FileChannel`:本地 `SocketChannel`:`TCP client端` `ServerSocketChannel`:`TCP Server端` `DatagramChannel`:`UDP`