阅读 69

IO模型之BIO/NIO

IO模型之BIO/NIO

前言

IO

I/O问题是任何编程语言都没办法回避的问题,I/O问题是整个人机交互的核心问题,因为I/O是机器获取和交换信息的主要渠道。

按照数据格式分为字节流IO(InputStream/OutputStream)字符流   (Writer/Reader)。
按照数据方式分为File文件操作IO和Socket网络操作IO。
IO本质上就是数据进出内存的过程。
复制代码

线程阻塞

线程阻塞的本质就是线程让出CPU资源,等待一定的条件被操作系统再次调度运行
线程阻塞的原因有以下几种:
1.Thread.sleep()
2.synchronized/Lock
3.wait()
4.线程执行IO操作或进行远程操作时,会因为等待相关资源而进入阻塞状态(System.in.read()如果用户没有向控制台输入数据,该线程会一直等待数据输入才从read()方法返回)
5.远程通信中,请求与服务器建立连接时,即当线程执行Socket的带参构造方法时,或进行socket的connect()方法时,会进入阻塞状态,直达连接成功
6.线程从Socket的输入流读取数据时,如果没有足够的数据,就会进入阻塞状态,直到读到了足够的数据,或者到达输入流的末尾,或者出现了异常,才从输入流的read()方法返回或异常中断
输入流中有多少数据才算足够呢?这要看线程执行的read()方法的类型
int read(); 只要输入流中有一个字节,就算足够。
int read(byte[] buff); 只要输入流中的字节数目与参数buff数组的长度相同,就算足够。
String readLine(); 只要输入流中有一行字符串,就算足够,InputStream类并没有readLine方法,在过滤流BufferedReader类中才有此方法
7.线程向Socket的输出流写一批数据时,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才从输出流的write()方法返回或异常中断
复制代码

计算机常识

一个计算机是有IO输入输出设备,存储器,控制器,运算器五部分组成
网卡(网络适配器)是主机和网络的接口,用于协调主机与网络间的数据、指令、信息的发送与接收。每块网卡出厂之后都有一个十六进制的网络节点地址,就是常说的MAC地址。
操作系统操作硬件,就是在操作文件,本质上就是在操作文件描述符(file descriptor),背后可能是对一个普通文件,fifo,管道,终端,键盘,显示器,甚至是一个网络连接。
网络连接 也被定义为一种类似的IO操作,类似于文件,也有文件描述符。当进行一次socket来进行一次通信,建立网络连接时,socket()的返回值就是文件操作符,有了这个文件操作符,就可以使用普通的文件操作函数来传输数据了。
一个网络输入操作通常包括两个阶段
  1.等待网络数据到达网卡--->读取到内核缓冲区,准备好数据
  2.从内核缓冲区复制数据到进程空间
复制代码

BIO/NIO模型图

BIO

BIO又叫做同步阻塞IO
传统BIO里面socket.read(),如果TCP RecvBuffer里面没有数据,函数会一直阻塞,直到收到数据,返回读到的数据。
BIO中有几个常见缺陷
 1.建立连接比较耗时,在客户端将数据传输到服务端之前,服务端的IO(输入流)一直阻塞,然后在服务端将返回值传输回来之前,客户端的IO(输入流)阻塞。也就是说在一次RPC调用开始到完成之前,这个连接一直被此次网络通信锁所占用,但是在实际的调用过程中,真正需要网络连接的只有中间的传输过程,在客户端写出和服务端读取并执行远端方法的这两个时间点,其实网络连接是空闲的,这样就浪费了网络资源。
 2.大量连接 由于BIO的阻塞问题,导致每次RPC调用都会占用一个连接,如果不加控制的话,将会频繁的创建连接并销毁。再进一步的讲,引入线程池来管理这种频繁的创建,但是也没有从根本上解决问题。如果服务消费者远大于提供者的情况,变会产生大量的服务连接。大量连接导致操作系统频繁的进行上下文时间片的切换,也会消耗大量资源,造成资源浪费。
BIO如果对应生活中的具体模型,可以想象成厨子烧菜,厨子每切好一盘菜,再进行烹饪,再切一盘,再烹饪,如此反复。
复制代码

NIO

NIO称为New IO,是一种同步非阻塞IO模型。
对于NIO,如果TCP RecvBuffer有数据,就把数据从网卡读到内存,并且返回给用户;反之则直接返回0,永远不会阻塞。
NIO真正解决的是阻塞问题,下面简单介绍一下NIO中几个常见的角色
Selector(选择器)/SocketChannel(通道)/Buffer(缓冲池)
BIO是面向Socket的连接,NIO是面向Channel的连接。SocketChannel相当于BIO中的Socket,真正建立并传输数据的管道,该管道可以被多个客户端线程请求使用,由于这个特点,极大改善了BIO中每连接每线程的问题。
Buffer是SocketChannel互通数据的对象,本质上是一块内存区域,由于SocketChannel不支持直接读取数据的,所有的读写操作必须通过Buffer来实现。
Selector是用来监控SocketChannel事件的,是实现非阻塞的核心,SocketChannel中有4个事件状态,写入完成时触发op_read可读事件,读数据完毕触发op_write,客户端与服务端建立连接时,客户端会收到op_connect事件,服务端触发op_accept事件。Selector本质上是轮询每一个SocketChannel,如果没有事件触发,轮询线程阻塞,如果有事件触发,返回对应的SocketChannel进行后续的业务处理。
NIO如果对应生活中的具体模型,可以想象厨子烧菜,每次只检查切好放到菜板上的菜,然后进行烹饪
 
复制代码
文章分类
后端
文章标签