浅析Java NIO底层原理及编写步骤
从历史发展角度看,一个新方法的出现,必然是先出现一种不太高效的方法,人们再加以改进。只有先理解了不太高效的方法,才能够理解新技术的本质。所以我们需要先了解一下什么是BIO?
传统的BIO采用流的方式进行传输,会造成一个问题:当客户端发送消息过于缓慢耗时太长,那么接收一方就会持续阻塞下去。也就是说如果发送方需要60s才能将数据传输完毕,那么接受方将会阻塞60s。为方便理解我们先看看一惯的BIO伪代码写法:
服务端
- 创建ServerSocket对象
- 死循环不断调用ServerSocket.accept,等待拿到Socket对象
- 开启线程 通过Socket对象读取输入流
- 读取并处理数据
- 写出响应数据
- 创建Socket对象
- 通过Socket对象获取输出流,写入数据
文章图片
【浅析Java NIO底层原理及编写步骤】此I/O模型为UNIX中定义的5种I/O模型的阻塞I/O模型
NIO
因此基于epoll的多路复用技术实现的NIO横空出世解决了BIO这一大病垢,NIO并不是建立scoket连接拿到文件描述符(fd)后就直接调用recvfrom函数进行数据读取,它则是将 fd给到epoll函数,由epoll函数基于自身的事件驱动机制,当检测到socket对应的fd 的数据可读时,触发回调函数告诉用户进程进行数据读取。
题外,select/poll也属于I/O复用模型(当然epoll也是),但为什么Java NIO底层不用它们进行实现呢?java培训原因是 select 它们是基于轮询的机制检测所有fd,发现有可读fd才返回,如果没有则阻塞于select,它的时间复杂度 O(n),且select 基于效率和性能的考虑,是有最大限制,默认为1024。而epoll的最大限制是受限于系统最大文件句柄数。poll的实现机制和select类似。
以上是对NIO底层原理的一个浅析,Java NIO 是基于这样的一种机制封装出一套类库来,供开发人员使用。不过原生的JavaNIO 类库还是过于繁琐不利于使用,因此才有了后面Netty的出世。如果要了解Netty,那必须要对原生Java NIO 类库的一些概念有一定熟悉。
JAVA原生NIO API 编写服务端和客户端大致步骤如下:
服务端:
- 创建ServerSocketChannel对象,同时将它的接收事件,注册到多路复用器(Selector)上
- 开启线程(Reactor)不断轮询就绪Channel
- 当存在就绪Channel集合时,多路复用器会返回SelectionKey 集合
- 轮询SelectionKey集合判断对应的事件类型
4.2 如果为读事件,则对该Channel进行数据读取
4.3 如果为写事件,则进行写数据,写完毕将该Channel写事件从多路复用器上移除。若写半包后TCP缓冲区已满,无法再写时,会继续将该Channel的写事件继续注册到多路复用器上,等待下次轮询。
文章图片
客户端
- 创建SocketChannel对象同时它接收事件,注册到多路复用器上
- 开启Reactor线程,不断轮询就绪Channel
- 当存在就绪Channel集合时,多路复用器会返回SelectionKey 集合
- 轮询SelectionKey集合判断对应的事件类型
- 写数据
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 事件代理
- Java|Java OpenCV图像处理之SIFT角点检测详解
- java中如何实现重建二叉树
- 数组常用方法一
- 【Hadoop踩雷】Mac下安装Hadoop3以及Java版本问题
- Java|Java基础——数组
- RxJava|RxJava 在Android项目中的使用(一)
- java之static、static|java之static、static final、final的区别与应用
- Java基础-高级特性-枚举实现状态机