BIO/NIO/AIO

这两个的基本区别网上很多文字都有写,这里稍微总结下:

BIO是同步阻塞的,客户端建立连接发送请求,服务端接收请求并等待数据处理完成后返回,从头到尾都是使用的同一个线程,因此该线程在此期间是完全阻塞住的,其他客户端请求只能再新建一个线程处理,当客户端数量多时,服务端会产生大量的线程,造成内存浪费和严重的线程切换开销。

NIO是同步非阻塞的,客户端的请求到来后,服务端的请求线程接受请求,然后将任务交给工作线程处理,请求线程返回,可以处理其他客户端的请求。当工作线程处理完后会返回响应给客户端。相比较BIO,NIO能有效减少线程的开销。

JAVANIO使用的Selevtor多路复用器来实现NIO,网上资料很多,但看了后总是觉得迷迷糊糊,难以真正理解。

1. 比如服务端工作线程完成后是客户端是如何感知的?

2. 他们之间的交互方式到底是一种什么样的状态?

3. 在我们平时使用的web服务,服务端使用BIO还是NIO的方式,客户端是不知道,这种客户端无感是怎么实现的?

这里按照我自己的理解,结合操作系统OS来讲一下JAVANIO的原理,结合了OS的机制后,理解上就清晰了很多。

同步/异步与阻塞/非阻塞

同步意味着步调一致,同步调用主动期待着返回结果,如果是阻塞模式,将等待返回完整的数据;如果是非阻塞模式,则立即返回,数据可能为空、可能不全、也可能是完整的。

异步调用不期待返回的结果,在调用时立即返回,并被动等待结果(通知机制),如果是阻塞模式,则被动等待过程中不做其他事(比如新建子线程执行任务,主线程join等待);如果是非阻塞则直接进行其他工作。但拿到的结果数据一定是完整的。

在进程级别上,同步异步与阻塞非阻塞是属于同义词的。在os的进程级别上,有四种阻塞状态:

  • 阻塞发送:调用send发送数据后等对方接收后才返回
  • 非阻塞发送:调用send发送数据后可以直接返回
  • 阻塞接收:调用receive一直等待只有有数据到来才返回
  • 非阻塞接收:调用receive直接返回,结果不一定有数据

发送和接收的阻塞方式是可以两两结合的,因此对于客户端和服务端而言,是可以分别具有不同行为的。

因此对于上面的问题3便可以回答了,客户端与服务端可以分别采用不同的策略。

请求与响应

首先,我们要知道客户端和服务端通信的一个流程。

进程发送请求,将数据写入到OS发送缓冲区,然后经过网卡发送到网络。

进程接收响应,通过读OS的读缓冲数据来进行数据的读取。

数据包通过MAC+IP+PORT的形式来绑定进程数据。

NIO

客户端的请求到达服务端会新建channel注册到Selector中,然后请求线程over,channel中包含着客户端的连接信息,但连接已经结束了;

Selector此时会调用注册了Accept事件的ServerSocketChannel进行处理,拿到这些对应客户端的channel。

服务端通过工作线程拿到数据后,会通过channel对客户端进行数据写入

响应的数据会通过os和网络发送到客户端主机上,这里会对客户端之间建立一条连接用于发送。

客户端的线程从读缓冲区中读到属于自己的数据,进行处理响应。

问题1,2解答

事件

对于NIO事件,accept只有服务端可以用,connect、write、read是客户端用的。