1. NIO类库简介

1.1 缓冲区Buffer

Buffer是一个对象,它包含了一些要写入或者要读出的数据。在NIO类库中加入Buffer对象,体现了新库和原来I/O的一个重要区别。在NIO库中,所有的数据都是用缓冲区处理的。在读取数据时,它是直接读取到缓冲区中的;在写入到缓冲区时。任何时候访问NIO中的数据,都是通过缓冲区进行操作。

缓冲区实质上是一个数组。通常它是一个字节数据(ByteBuffer),也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组,缓冲区还提供了对数据的结构化访问以及维护读写位置(limit)等信息。

  • ByteBuffer:字节缓冲区
  • CharBuffer:字符缓冲区
  • ShortBuffer:短整形缓冲区
  • IntBuffer:整形缓冲区
  • LongBuffer:长整形缓冲区
  • FloatBuffer:浮点型缓冲区
  • DoubleBuffer:双精度浮点型缓冲区

缓冲区的继承关系如下:

1.2 通道Channel

Channel是一个通道,它就像自来水管道一样,网络数据通过Channel读取和写入。通道与流不同之处在于通道它是双向的,流只是在一个方向上移动(一个流必须是InputStream或者OutputStream的子类),而通道可以用于读、写或者二者同时进行。因为Channel是全双工的,所以它可以比流更加映射底层操作系统地API。从类图中可以看出,实际上Channel可以分为两大类:用于网络读写的SelectabaleChannel和用于文件操作的FileChannel。ServerSocketChannel是一个可以监听新进来的TCP连接的通道,就像标准IO中的ServerSocket一样。

1.3 多路复用器Selector

Select会不断地轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel集合,进行后续的I/O操作。一个多路复用器Selector可以同时轮询多个Channel,由于JDK使用了epoll()代替传统的select实现,所以它并没有最大连接句柄1024/2048的轮询,就可以接入成千上万的客户端。

2. NIO服务端序列图

一。 打开ServerSocketChannel,用于监听客户端的连接。

ServerSocketChannel servChannel=ServerSocketChannel.open();

二。绑定监听端口,设置连接为非阻塞状态。

servChannel.configureBlocking(false);
servChannel.socket().bind(new InetSocketAddress(port), 1024);

三。创建Reactor线程,创建多路复用器并启动线程。

Selector selector = Selector.open();

四。将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听ACCEPT事件

servChannel.register(selector, SelectionKey.OP_ACCEPT);

五。多路复用器在线程run方法的无线循环体内轮询准备就绪的Key。

while (!stop) {try {selector.select(1000);Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> it = selectedKeys.iterator();SelectionKey key = null;while (it.hasNext()) {key = it.next();it.remove();try {handleInput(key);} catch (Exception e) {if (key != null) {key.cancel();if (key.channel() != null)key.channel().close();}}}} catch (Throwable t) {t.printStackTrace();}}

六。多路复用器监听到有新的客户端接入,处理新的接入请求,完成TCP三次握手,建立物理链路。

 if (key.isAcceptable()) {// Accept the new connectionServerSocketChannel ssc = (ServerSocketChannel) key.channel();SocketChannel sc = ssc.accept();
}

七。设置客户端链路为非阻塞模式

sc.configureBlocking(false);

八。将接入的客户端连接注册到Reactor线程的多路复用器上,监听读操作,读取客户端发送的网络消息。

sc.register(selector, SelectionKey.OP_READ);

九。异步读取客户端请求消息到缓冲区。

 if (key.isReadable()) {// Read the dataSocketChannel sc = (SocketChannel) key.channel();ByteBuffer readBuffer = ByteBuffer.allocate(1024);int readBytes = sc.read(readBuffer);.....}

十。对ByteBuffer进行编码解码,如果有半包消息指针reset,继续读取后续的报文,将解码成功的消息封装成Task,投递到业务线程池中,进行业务逻辑编排。

Object message=null;
while(buffer.hasRemain()){bytebuffer.mark();Object message=decode(byteBuffer);if(message==null){byteBufer.reset();break;}messageList.add(message);
}
if(!bytebuffer.hasRemain()){byteBuffer.clear();
}elsebyteBuffer.compact();if(messageList!=null & !messageList.isEmpty()){for(Obbject messageE:messagList){handlerTask(messageE)}
}

十一。将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发送给客户端。

socketChannel.write(buffer).

3. NIO客户端序列图

一。打开SocketChannel,绑定客户端本地地址。

SocketChannel clientChannel = SocketChannel.open();

二。设置SocketChannel为非阻塞模式,同时设置客户端连接的TCP参数。

socketChannel.configureBlocking(false);
socketChannel.socket().setReuseAddress(true);

三。异步连接服务器。判断是否连接成功,如果连接成功,则直接注册读取状态到多路复用器中,如果当前没有连接成功,则向Reactor的多路复用器注册OP_CONNECT状态为,监听服务器端的TCP ACK应答。

// 如果直接连接成功,则注册到多路复用器上,发送请求消息,读应答if (socketChannel.connect(new InetSocketAddress(host, port))) {socketChannel.register(selector, SelectionKey.OP_READ);doWrite(socketChannel);} elsesocketChannel.register(selector, SelectionKey.OP_CONNECT);

四。创建Reactor线程,创建多路复用器并启动线程。

Selector selector = Selector.open();

五。多路复用器在线程run方法的无线循环体内轮询准备就绪的Key。

while (!stop) {try {selector.select(1000);Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> it = selectedKeys.iterator();SelectionKey key = null;while (it.hasNext()) {key = it.next();it.remove();try {handleInput(key);} catch (Exception e) {if (key != null) {key.cancel();if (key.channel() != null)key.channel().close();}}}} catch (Throwable t) {t.printStackTrace();}}六。接受connect事件处理。判断连接结果,如果连接成功,注册连接事件到多路复用器。注册读事件到多路复用器中。
```java
if (key.isConnectable()) {if (sc.finishConnect()) {sc.register(selector, SelectionKey.OP_READ);doWrite(sc);} elseSystem.exit(1);// 连接失败,进程退出}

七。异步读取客户端请求消息到缓冲区。

 if (key.isReadable()) {// Read the dataSocketChannel sc = (SocketChannel) key.channel();ByteBuffer readBuffer = ByteBuffer.allocate(1024);int readBytes = sc.read(readBuffer);.....}

八。对ByteBuffer进行编码解码,如果有半包消息指针reset,继续读取后续的报文,将解码成功的消息封装成Task,投递到业务线程池中,进行业务逻辑编排。

Object message=null;
while(buffer.hasRemain()){bytebuffer.mark();Object message=decode(byteBuffer);if(message==null){byteBufer.reset();break;}messageList.add(message);
}
if(!bytebuffer.hasRemain()){byteBuffer.clear();
}elsebyteBuffer.compact();if(messageList!=null & !messageList.isEmpty()){for(Obbject messageE:messagList){handlerTask(messageE)}
}

九。将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发送给客户端。

socketChannel.write(buffer).

4. 总结

通过源码分析,我们发现NIO编程的难度确实比同步阻塞BIO的大很多,我们的NIO程序中还没有考虑“半包读”和“半包写”,如果加上这些,代码会更加复杂。使用NIO编程的优点如下:

  • 客户端发起连接的操作是异步的,可以通过多路复用器注册OP_CONNECT等待后续结果,不需要像之前的客户端那样被同步阻塞。
  • SocketChannel的读写操作是异步的,如果没有可读写的数据它不会等待,直接返回,这样I/O通信线程就可以处理其他链路,不需要同步等待这个链路可用。
  • 线程模型的优化:由于JDK的Selector在Linux等主流操作系统上通过epoll实现,它没有连接句柄的限制。

NIO详解(四):NIO编程相关推荐

  1. java nio详解,Java NIO API详解

    Java NIO API详解 在JDK 1.4以前,Java的IO操作集中在java.io这个包中,是基于流的阻塞(blocking)API.对于大多数应用来说,这样的API使用很方 便,然而,一些对 ...

  2. NIO详解以及NIO的文件IO操作

    一.NIO概述 java.nio全称java non-blockingIO,是指JDK1.4开始提供的新API.从JDK1.4开始,Java提供了一系列改进的输入/输出的新特性,被统称为NIO(即Ne ...

  3. Java基础——Java NIO详解(一)

    一.基本概念 1.I/0简介 I/O即输入输出,是计算机与外界世界的一个借口.IO操作的实际主题是操作系统.在java编程中,一般使用流的方式来处理IO,所有的IO都被视作是单个字节的移动,通过str ...

  4. Java基础——Java NIO详解(二)

    一.简介 在我的上一篇文章Java NIO详解(一)中介绍了关于标准输入输出NIO相关知识, 本篇将重点介绍基于网络编程NIO(异步IO). 二.异步IO 异步 I/O 是一种没有阻塞地读写数据的方法 ...

  5. 详解 Java NIO

    详解 Java NIO 文件的抽象化表示,字节流以及字符流的文件操作等属于传统 IO 的相关内容,我们已经在前面的文章进行了较为深刻的学习了. 但是传统的 IO 流还是有很多缺陷的,尤其它的阻塞性加上 ...

  6. mybatis 鉴别其_MyBatis之Mapper XML 文件详解(四)-JDBC 类型和嵌套查询

    MyBatis之Mapper XML 文件详解(四)-JDBC 类型和嵌套查询 白玉 IT哈哈 支持的 JDBC 类型 为了未来的参考,MyBatis 通过包含的 jdbcType 枚举型,支持下面的 ...

  7. 详解c语言编程库题,详解C语言编程

    C语言作为编程语言,其诞生已经很早,但是在编程语言多样化的今天,C仍然高居TIOBE编程语言排行榜的第一位(2014年5月),而C++语言排位第四.而位居第二位的Java本身就是脱胎于C++语言,第三 ...

  8. 【STM32-I2C学习总结】STM32:硬件-IIC详解 , 固件库编程 , 手把手教你实现IIC

    STM32:硬件-IIC详解 , 固件库编程 , 手把手教你实现IIC 一 .I2C物理层 二.协议层 1.I2C基本读写过程 (1)主机写数据到从机 (2)主机由从机中读数据 (3)I2C 通讯复合 ...

  9. python import io_详解Python IO编程

    文件读写 读文件 try: # windows下utf8 f = open('./README.md', 'r', encoding='utf8', errors='ignore') print(f. ...

  10. linux 进程间通信 dbus-glib【实例】详解四(上) C库 dbus-glib 使用(附代码)(编写接口描述文件.xml,dbus-binding-tool工具生成绑定文件)(列集散集函数)

    linux 进程间通信 dbus-glib[实例]详解一(附代码)(d-feet工具使用) linux 进程间通信 dbus-glib[实例]详解二(上) 消息和消息总线(附代码) linux 进程间 ...

最新文章

  1. 【小练习06】HTML+CSS--教学大讲堂
  2. 华为轮值主席鸿蒙,鸿蒙2.0已开源 华为轮值董事长:今年至少3亿设备搭载鸿蒙系统...
  3. commons-lang3工具类学习(二)
  4. 计算机视觉、机器学习相关领域论文和源代码大集合--持续更新……
  5. django 日志配置
  6. ADO.NET与ORM的比较(4):EntityFramework实现CRUD
  7. Hadoop-2.2.0中文文档——MapReduce 下一代 -——集群配置
  8. Python破解协议密码
  9. JAVA练习55-最小的k个数
  10. 如何将音频从视频分离到单独的音轨?
  11. 特洛伊木马程序_历史著名的特洛伊木马计,希腊的人造礼物
  12. RQNOJ:PID4 数列
  13. 《大秦帝国之崛起》看后感
  14. 【转】HTML5前端性能优化——浏览器兼容与前端性能优化
  15. win10如何打开摄像头_win10录屏软件哪个好?可录摄像头不限时长的视频录制方法...
  16. Lua:01---Lua语言介绍、运行Lua程序(lua解释器)
  17. 双球坐标系_2.1 天球坐标系和地球坐标系
  18. Package OpenCV not found? Let’s Find It.
  19. 开发部工程师工作指导及规范
  20. 酷米SOP S10手机刷机固件原厂维修线刷包附教程

热门文章

  1. Vue蚂蜂窝Vue-cli+webpack做的
  2. Get和Post的参数传值
  3. UE4 AR开发笔记
  4. 【使用 DOM】为DOM元素设置样式
  5. 一款简洁大气的jquery日期日历插件
  6. ClientDataSet 探讨
  7. js 正则判断字符串是否为字母或数字
  8. android 固定中间焦点,在Android上将相机焦点设置为受控固定距离
  9. 网络和计算机管理制度,网络和计算机使用管理制度
  10. 求关系模式r的所有候选码_关系数据理论基础概念