这篇文章主要是阅读了一些关于NIO的文章,对一些重要的部分进行了摘取总结。
BIO、NIO、AIO的不同
BIO:同步阻塞IO模式,线程发起IO请求后,一直阻塞IO,直到缓冲区数据就绪后,再进行下一步操作。
NIO:是同步非阻塞IO,线程发起IO请求后,立即返回。同步体现在selector仍然要去轮询判断channel是否准备好,非阻塞体现在这个过程中处理用户线程不会一直等待,可以去做其他的事情,但是要定时轮询IO缓存区数据是否准备好。
NIO主要有buffer、channel、selector三个组件,通过零拷贝的buffer获取数
AIO是异步非阻塞IO模型。在上述NIO实现中,需要用户线程定时轮询,去检查IO缓冲区数据是否准备就绪,占用应用程序线程资源,其实轮询也是阻塞的,它需要查询哪些IO就绪了。而真正的理想的异步非阻塞IO应该让内核系统完成,用户线程只需告诉内核,当缓冲区就绪后,通知我或者执行回调函数。
BIO存在的问题
在BIO模式中,socket.accept()、socket.read()、socket.write()三个主要函数都是同步阻塞的。当一个连接处理IO的时候,系统是阻塞的,要想处理多个连接,就要使用多线程。但是,当面对数万级别的连接时,传统的BIO模型就不行了,太消耗资源了。
NIO与IO的区别
NIO以块的方式处理数据,但是IO是以最基础的字节流的形式进行写入和读出。
NIO的通道是双向的,但是IO中的流是单向的。
NIO采用的是多路复用的IO模型,普通的IO用的就是阻塞的IO模型。
NIO的工作流程
1) 首先先创建ServerSocketChannel对象和真正处理数据的线程池。
2)然后给刚刚创建的ServerSocketChannel对象进行绑定一个对用的端口,然后设置为非阻塞。
3)然后创建Selector对象并打开,然后把这个ServerSocketChannel对象注册到Selector中,并设置好监听的事件,监听SelectionKey.OP_ACCEPT。
4)Selector对象死循环监听每一个Channel通道的事件,循环执行Selector.select()方法,轮询就绪的方法。
5)从Selector中获取所有的SelectorKey,如果SelectorKey是处于OP_ACCEPT状态,说明有新的客户端接入,调用ServerSocketChannel.accept接收新的客户端。
6)然后把这个接收的新客户端的Channel通道注册到ServerSocketChannel上,并且把之前的OP_ACCEPT状态改为SelectionKey.OP_READ读取事件状态,并且设置为非阻塞,然后把当前的这个SelectorKey给移除掉,说明这个事件完成了。
7)如果第五步的事件不是OP_ACCEPT,那就是OP_READ读取数据的事件状态。然后调用对应的机制。
不选择JAVA原生NIO编程的原因
NIO的类库和API繁琐,使用麻烦,你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。
需要具备其他的额外技能做铺垫,例如要熟悉Java多线程编程。
可靠性能力补齐,工作量和难度都非常大。比如说拆包闭包问题。
JDK NIO的BUG,例如臭名昭著的epoll bug,它会导致Selector空轮询,最终导致CPU 100%。
为什么选择Netty
API简单,开发门槛低。
定制能力强,可以通过ChannelHandler对通信框架进行灵活的扩展。