Netty4核心原理学习之JAVA I/O演讲之路

1、什么是I/O

我所理解的I/O操作,简而言之就是流,包括往流中读出数据,系统调用read,写入数据,系统调用write。操作系统都是通过内核创建文件描述符FD(File Descriptor,FD)来标识的,一个FD就是一个非负整数,所以对这个额整数的操作就是对这个文件(流)的操作。

2、/O交互流程

I/O交互流程分为两阶段:首先是经过内核空间,也就是有操作系统处理;紧接着就是到用户空间,也就是交由应用程序。

在这里插入图片描述

内核空间存放的是内核代码和数据,而进程的用户空间存放的是用户程序的代码和数据,不管是内核空间和用户空间,它们都处于虚拟空间中,两者都不难简单的使用指针传递数据,都必须通过系统调用请求Kernel来协助完成I/O操作,内核会为每一个I/O设备一个缓冲区,用户空间的数据可能被换出,所以当内核空间使用用户空间的指针是,对应的数据可能不在内存中。

对于一个输入操作来说,进程调用I/O操作后,内核会先看缓冲区中有没有相应的缓存数据,如果没有再到设备中读取。如果有数据,内核缓冲区则直接复制进程空间。因此一个输入操作通常包括两个不同阶段:

  1. 等到网络数据到达网卡,然后将数据读取都内核缓冲区
  2. 从内核缓冲区复制数据,然后拷贝到用户空间。

3、五种I/O通信模型

3.1、阻塞I/O模型

在这里插入图片描述
用户进程首先调用recvfrom函数,内核开始进行第一阶段:准备数据,但是此时网络I/O并未接收到完整的UDP包,这个时候就需要足够的数据到来,此时用户进程会被阻塞,当数据准备好,内核会直接将数据拷贝到用户空间,返回结果,阻塞解除。
特点:在I/O执行的两个阶段(等待数据和拷贝数据)都被阻塞。

3.2、非阻塞I/O模型

在这里插入图片描述
用户进程首先发起一个read操作,如果内核中数据没有准备好,并不会阻塞用户进程,并且立刻返回一个error,用户进程得到结果后,又再次发送read操作,一旦内核中数据准备好了,并且再次受到用户进程的系统调用,那么会马上将数据拷贝到用户内存。
特点:用户进程需要不断的主动询问内核(Kernel)数据准备好了没有

3.3、多路复用I/O模型

在这里插入图片描述
多个进程的I/O可以注册到一个复用器(Selector)上,当用户进程调用该Selector,Selector会监听注册起来的I/O,如果监听的I/O在内核缓冲区没有数据,那么此时selector调用进程会被阻塞,而当任一I/O在内核缓冲区有可读数据时,select调用会立即返回,而后select调用进程可以自己或通知另外的进程再起发起读取I/O,读取内核中准备好的数据,多个进程注册I/O后,只有一个select调用进程会被阻塞。
特点:对于每一个Socket,一般都设置成非阻塞,但是整个用户的进程其实是一直被阻塞的,只不过进是被select函数阻塞,而不是被Socket I/O阻塞。

3.4、信号驱动I/O模型

在这里插入图片描述
信号驱动I/O就是指进程预先告知内核,向内核注册一个信号处理函数,然后用户进程返回阻塞,党内和数据就绪时会发送一个信号给进程,用户进程便在信号处理函数中调用I/O读取数据。
信号驱动I/O并未实现真正的异步,实际在内核拷贝数据到用户进程的过程还是阻塞的。
特点:并不符合异步I/O要求,只能算伪异步,并且实际中不常用。

3.5、异步I/O模型

在这里插入图片描述
前面的四种同步IO模型中,不管是哪一种方式,数据在通过内核空间向应用空间拷贝的这个过程中,用户进程都是阻塞的,产生这个问题的根源是由于触发数据从内核空间向应用空间拷贝的这个行为是应用进程来发起,所以应用进程在整个过程中一定是阻塞的。异步I/O是将数据拷贝的这整个事情交给系统来做,而不是应用进程,应用进程只是作为整个事情的发起人和最终结果的处理人,对整个过程完全不进行参与,保证了应用进程在整个处理过程的非阻塞。
特点:真正实现了异步I/O,是五种I/O模型中唯一的异步模型。

4、各I/O模型的对比与总结

在这里插入图片描述
从上图可以看出,阻塞程度:阻塞I/O>非阻塞I/O>多路复用I/O>信号驱动I/O>异步I/O,效率由低到高。

各I/O模型之间的差异
在这里插入图片描述

5、BIO到NIO

BIO:面向流、阻塞I/O
NIO:面向缓冲区、非阻塞I/O、选择器触发

误区:NIO在执行recvfrom的时间,如果内核中的数据没有准备好是不会阻塞进程,但是当内核中数据准备好的时候,recvfrom会将数据从内核拷贝到用户内存中,这个时候进程会被阻塞。

BIO/NIO区别(阻塞与非阻塞)
JavaBIO的各种流都是被阻塞的,当一个线程调用read()或者write()时,该线程都会被阻塞,直到一些数据被读取或者完全写入。 举个例子:如 闯关游戏,你必须闯过这一关你才能进入下一关。

JavaNIO:线程从某通道(channel)发送请求读取数据,但是它仅能得到目前可用的数据,没有数据则不会读取,而不是保持线程阻塞,知道数据被读取时,都可以做任何事情。

public class inputStream {
    public static void main(String[] args) throws IOException {
        FileInputStream input = new FileInputStream("C://Users//Administrator//Desktop//inputstream.txt");
        BufferedReader reader = new BufferedReader(new InputStreamReader(input));
        String name = reader.readLine();
        String age = reader.readLine();
        String phone = reader.readLine();
        String str;
        while (null != (str = reader.readLine())) {
            System.out.println(str);
        }
    }
}

很明显可以看出,BIO是必须通过判断是否有read.readLine()方法返回,才能得到文本行是否已经读取完,一旦read.readLine()方法返回,此时readLine会阻塞直到整行读取完毕。由此可见BIO处理状态由程序执行多久决定。

public class ByteBufferNIO {
    public static void main(String[] args) throws IOException {
        FileInputStream input = new FileInputStream("C://Users//Administrator//Desktop//inputstream.txt");
        FileChannel channel = input.getChannel();
        //allocate()方法用于分配缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(48);
        int bytesRead = channel.read(buffer);
        //此时不并不知道缓冲区是否存在有数据 那么就要去轮询查看缓冲区是否有数据
        while (buffer.hasRemaining()) {
            bytesRead = channel.read(buffer);
        }
    }
}

NIO从通道读取字节到ByteBuffer,这个方法返回时,并不知道所需要的数据是否在缓冲区,所以此时你必须跟踪有多少数据进入缓冲区,如果缓冲区已满,它可以被处理。
NIO可以只使用一个(或多个)单线程管理多个通道,而BIO只能用一个线程来处理。
如下图所示:
在这里插入图片描述
在这里插入图片描述

参考

《Netty 4核心原理与手写RPC框架实战》 谭勇德(Tom)著

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值