Java网络编程2:初识BIO、NIO、AIO

本文参考:https://blog.csdn.net/qq_21125183/article/details/86161620

     https://blog.csdn.net/a724888/article/details/70598648

一、BIO(Block I/O)阻塞I/O

  从上篇博文指导,Java网络编程分为基于TCP和基于UDP的,再这里默认的都是基于TCP的网络编程。一般的网络编程过程是,客户端创建Socket对象,发起连接,向服务器发起读写请求。服务器通过ServerSocket对象监听端口。建立连接后,客户端发起读请求,这是在服务器端需要先把待读内容复制到用户空间然后再复制传输到客户端。就是说在发起对请求时,需要两个复制过程,所以称之为阻塞也即BIO。图片取自:https://blog.csdn.net/qq_21125183/article/details/86161620 侵删。

  传统的BIO,都是客户端发送一个请求,服务器就建立一个链接,这样如果另一个客户端发送请求只能等当前请求完成后才能建立连接。于是就出现服务端每接受一个请求就开一个线程

这样就可以同时接受多个客户端的访问。但是随着客户端的增多,就会不断地开线程,这样会吃掉服务器的大量内存,例如32位的电脑,内存只有4G大小,出去操作系统,可用内存只有2G左右,假设一个线程分配1M的内存空间,那么最多可以建立2048个线程,所以这时候就可以改进使用线程池加以改进

使用线程池的好处可以控制线程的个数,即利用了多线程的处理,又控制了系统的资源消耗。但是问题又来了,用户数远大线程池中线程的个数,请求一般会存在等待队列中,但是有丢失的风险。这时就学要思考如何才能优化,解决这样的问题。最后就出现了NIO,通过优化阻塞

代码可以参考:https://blog.csdn.net/a724888/article/details/70313748

二、NIO(New I/O)

1、NIO原理理解 

 由上文可知,为了加快客户端访问服务器的速度,开发出了NIO,非阻塞I/O。

由上图可知,阻塞的过程有两个部分,等待数据和复制数据,优化的部分是等待数据部分。再Linux中有select,我的理解是一个注册器,当有请求过来时,会在select中添加,然后就开始等待数据操作,而不是发起都操作时才等待数据操作。这样可以一直扫描select中注册的线程状态,如果数据准备好了就可以进行数据传输了。

1、进行select/epoll系统调用,查询可以读的连接。kernel会查询所有select的可查询socket列表,当任何一个socket中的数据准备好了,select就会返回。当用户进程调用了select,那么整个线程会被block(阻塞掉)。
2、用户线程获得了目标连接后,发起read系统调用,用户线程阻塞。内核开始复制数据。

2、介绍

  NIO提供了与传统BIO模型中的Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel两种不同的套接字通道实现。SocketChannel可以看作是 socket 的一个完善类,除了提供 Socket 的相关功能外,还提供了许多其他特性,如后面要讲到的向选择器注册的功能。新增的着两种通道都支持阻塞和非阻塞两种模式。

3、缓冲区buffer

  Buffer是一个对象,包含一些要写入或者读出的数据。在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,也是写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。缓冲区实际上是一个数组,并提供了对数据结构化访问以及维护读写位置等信息。具体的缓存区有这些:ByteBuffe、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。他们实现了相同的接口:Buffer。

4、通道channel

  我们对数据的读取和写入要通过Channel,它就像水管一样,是一个通道。通道不同于流的地方就是通道是双向的,可以用于读、写和同时读写操作。底层的操作系统的通道一般都是全双工的,所以全双工的Channel比流能更好的映射底层操作系统的API。网络数据通过Channel读取和写入,通道与流不同之处在于通道时双向的,而流只在一个方向移动(一个流必须时InputStream和OutputStream的子类),而通道可以用于读、写或者两者同时进行,最关键的时可以与多路复用器结合起来,有多种的状态位,方便多路复用器去识别。    

5、多路复用器selector

  Selector是Java NIO 编程的基础。 Selector提供选择已经就绪的任务的能力:Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。

  Selector就类似于一个管理者(Master),管理了成千上万个管道,然后轮询哪个管道的数据已经准备好,通知CPU执行IO的数据或者写入操作。

  Selector模式:当Channel管道注册到Selector选择器以后,Selector会分配给每个管道一个key值,相当于标签用来唯一标识Channel。Selector选择器是以轮询的方式进行查找注册的所有Channel,当我们的Channel准备就绪或者监听到相应的事件状态的时候,selector就会识别这个事件状态,并且通过相应的key值找到相应的Channel,进行相关的数据处理操作(从Channel中读取数据或者写入数据,写到我们的数据缓冲区中)。

  每个Channel都会选择对选择器进行注册不同的事件状态,以便选择器进行查找。

SelectionKey.OP_CONNECNT: 连接就绪事件,表示客户端与服务器的连接已经建立成功
SelectionKey.OP_ACCEPT: 接收连接事件,表示服务器监听到了客户连接,服务器可以接收这个连接了
SelectionKey.OP_READ: 读就绪事件,表示通道中已经有了可读的数据,可以执行读操作了
SelectionKey.OP_WRITE: 写就绪事件,表示已经可以向通道写数据了

三、AIO

1、当用户线程调用了read系统调用,立刻就可以开始去做其它的事,用户线程不阻塞。
2、内核(kernel)就开始了IO的第一个阶段:准备数据。当kernel一直等到数据准备好了,它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存)。
3、kernel会给用户线程发送一个信号(signal),或者回调用户线程注册的回调接口,告诉用户线程read操作完成了。
4、用户线程读取用户缓冲区的数据,完成后续的业务操作。
  • AsynchronousServerSocketChannel
  • AsynchronousSocketChannel
  • AsynchronousChannelGroup

四、总结

如果你想吃一份宫保鸡丁盖饭:

同步阻塞:你到饭馆点餐,然后在那等着,还要一边喊:好了没啊!
同步非阻塞:在饭馆点完餐,就去遛狗了。不过溜一会儿,就回饭馆喊一声:好了没啊!(这里的遛狗和点餐可以理解为同一个线程中的两个通道
异步阻塞:遛狗的时候,接到饭馆电话,说饭做好了,让您亲自去拿。(这里的遛狗和点餐是分别在不同的线程中
异步非阻塞:饭馆打电话说,我们知道您的位置,一会给你送过来,安心遛狗就可以了。

posted @ 2019-07-22 10:02  悦风旗下  阅读(179)  评论(0编辑  收藏  举报