进程中的IP调用步骤大致可以分为以下四步:

  1. 进程向操作系统请求数据

  2. 操作系统把外部数据加载到内核的缓冲区中

  3. 操作系统把内核的缓冲区拷贝到进程的缓冲区

  4. 进程获得数据完成自己的功能

当操作系统把外部数据放到进程缓冲区的这段时间(即2/3步),如果应用进程是挂起等待的,那么就算同步IO,反之就是异步IO也就是AIO

NIO

NIO本身是基于事件驱动思想来完成的,其主要想解决的是BIO的大并发问题:在使用同步I/O的网络应用中,如果同时处理多个客户端请求,或是在客户端要同时和多个服务器进行通讯,就必须使用多线程来处理。也就是说,将每一个客户端请求分配给一个线程来单独处理。这样做虽然可以达到我们的要求,但同时有会带来另外一个问题。由于每创建一个线程,就要为这个线程分配一定的内存空间,而操作系统本身也是对线程的总数有一定的限制。如果客户端的请求过多,服务端程序可能会因为不堪重负而拒绝客户端的请求,甚至宕机
NIO基于Reactor,当socket有流可读或可写入socket时,操作系统会相应的通知应用程序进行处理,应用再讲流读取到缓冲区或写入操作系统。也就是说,这个时候,已经不是一个连接对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。
BIO和NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者只使用少量的线程,每个连接共用一个线程。
NIO的最重要的地方是当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。
在NIO的处理方式中,当一个请求来的话,开启线程进行处理,可能会等待后端应用的资源,其实这个线程就被阻塞了,当并发上来的话,还是会有BIO一样的问题。
HTTP/1.1出现后,有了http长连接,这样除了超时和指明特定关闭的http header外,这个链接是一直打开的状态的,这样在NIO处理中可以进一步的优化,在后端资源中可以实现资源池或者队列,当请求来的话,开启的线程把请求和请求数据传送给后端资源池或者队列里面就返回,并且在全局的地方保持住这个现场(哪个连接的哪个请求等),这样前面的线程还是可以去接受其他的请求,而后端应用的处理只需要执行队列里面的就可以了,这样请求处理和后端应用是异步的,当后端处理完,到全局得到现场,产生响应,这个就实现了异步处理。

AIO

与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。在JDK1.7中,这部分内容被称作NIO.2,主要在java.nio.channels包下面增加了下面四个异步通道:

  • AsynchronousSocketChannel

  • AsynchronousServerSocketChannel

  • AsynchronousFileChannel

  • AsynchronousDatagramChannel

其中的read./write方法,会返回一个带回调函数的对象,当执行完读取/写入操作后,直接回调函数。

BIO是一个连接一个线程
NIO是一个请求一个线程
AIO是一个有效请求一个线程
适用场景:
BIO方式适用于连接数目较少且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,程序直观易理解。
NIO方式适用于连接数目较多且连接较短的架构,比如聊天服务器,并发局限于应用中,编程比较复杂。
AIO方式适用于连接数目多且连接比较长的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂。

IO

1、Linux I/O流程NIO和AIO - 图2

等待数据准备好
从内核向进程复制数据

2、五种I/O模型

  1. 阻塞式模型

  2. 非阻塞式模型

  3. I/O复用

  4. 信号驱动式I/O

  5. 异步I/O

2.1 阻塞式I/O

NIO和AIO - 图3

2.2 非阻塞式

NIO和AIO - 图4

2.3 I/O复用

NIO和AIO - 图5

2.4 信号驱动I/O

NIO和AIO - 图6

2.5 异步I/O

NIO和AIO - 图7

2.6 各种I/O比较

NIO和AIO - 图8

lPOSIX标准将同步I/O和异步I/O定义为:

Ø同步I/O操作:导致请求进程阻塞,直到I/O操作完成。

Ø异步I/O操作:不导致请求进程阻塞。

java I/O模型

blocking I/O

NIO和AIO - 图9

NIO

  1. Buffers

    1. 缓冲区,数据都会在这里进行读写,几个属性position、limit、capacity

    2. capacity代表容量,一旦设定就不能更改,当写满后,需要清空才能重新写入

    3. 从写操作模式到读操作模式切换的时候(flip),position会归零,这样就可以从头开始读写了

    4. 写操作模式下,limit代表的是最大能写入的数据,这个时候limit等于capacity

    5. 写结束后,切换到读模式,此时的limit等于buffer中实际数据大小,因为buffer不一定被写满了

    6. Non-directByteBuffer,HeapByteBuffer标准的java类,维护一份byte[] 在jvm堆上,创建开销小。DirectByteBuffer,底层存储在非JVM堆上,通过native代码操作,jvm参数-XX:maxDirectMemorySize=11,创建开销大

    7. NIO和AIO - 图10NIO和AIO - 图11

  2. Channels

    1. 所有的NIO操作始于通道,通道是数据来源或数据写入的目的地,channel的实现有FileChannel(文件通道,用于文件的读和写),DatagramChannel(用于UDP连接的接收和发送),SocketChannel(TCP连接通道,客户端用),ServerSocketChannel(TCP对应的服务端,监听某个端口进来的请求)

    2. 读操作就是将数据从channel读取到buffer中

    3. 写操作就是将buffer中数据写入到channel中

  3. Selectors

    1. selector是java NIO中的一个组件,用于检查一个或多个NIO channel的状态是否处于活动状态

    2. 这样就可以实现单线程管理多个channels,也就是可以管理多个网络连接

    3. selectionKey-维护了selector和注册的channel直接的关系,通过Selector.select方法获取活动状态的channel,然后遍历进行处理。

NIO和AIO - 图12

NIO和AIO - 图13

  1. Set<SelectionKey>selectedKeys = selector.selectedKeys();
  2. Iterator<SelectionKey>keyIterator = selectedKeys.iterator();
  3. while(keyIterator.hasNext()){
  4. SelectionKey key = keyIterator.next();
  5. if(key.isAcceptable()) {
  6. // a connection was accepted by aServerSocketChannel.
  7. } else if (key.isConnectable()) {
  8. // a connection was established with aremote server.
  9. } else if (key.isReadable()) {
  10. // a channel is ready for reading
  11. } else if (key.isWritable()) {
  12. // a channel is ready for writing
  13. }
  14. keyIterator.remove();
  15. }

NIO带来了什么

  1. 事件驱动模型

    1. 避免多线程

    2. 单线程处理多任务

  2. 非阻塞IO,IO读写不再阻塞,而是返回0

  3. 基于block的传输,通常比基于流的传输更高效

  4. 更高级的IO函数,zero-copy

  5. IO多路复用大大提高了java网络应用的可伸缩性和实用性