文档章节

Netty精粹之JAVA NIO开发需要知道的

Float_Luuu
 Float_Luuu
发布于 2016/02/07 22:52
字数 2617
阅读 14991
收藏 130

学习Netty框架以及相关源码也有一小段时间了,恰逢今天除夕,写篇文章总结一下。Netty是个高效的JAVA NIO框架,总体框架基于异步非阻塞的设计,基于网络IO事件驱动,主要贡献在于可以让用户基于Netty提供的API快速开发高性能、高可靠性的网络应用。这篇文章主要是介绍Netty框架的基础技术——JAVA NIO。这时候可能会有同学会有点小疑问,是异步IO(AIO)么?然而并不是,虽然JDK7也提供了异步IO(AIO)的接口,但是Netty曾经尝试过某个小版本,但是效果和NIO相比并没有什么优势,因此后面的版本Netty也把对AIO的支持废弃了,今天我们就来扒一下JAVA NIO。


四种IO模型简述

我们先从四种IO模型开始扒起,常见的IO模型有四种(这四种模型在网络上也有很多很多的资料,为较少篇幅本片将这部分内容压缩一下):

  1. 同步阻塞(Blocking IO):最简单的一种IO模型,用户线程在进行IO操作的时候通常是个系统调用,用户线程会由用户空间进入内核空间,内核空间数据包准备好后会将数据拷贝到用户空间,这个时候线程在用户态继续执行。

  2. 同步非阻塞(Non-blocking IO):同步非阻塞IO即在同步阻塞的基础之上将socket设置为NONBLOCK。这样用户线程在发起IO操作之后可以立即返回,但是用户线程需要不断轮询来请求数据。

  3. IO多路复用(IO Multiplexing):即Reactor设计模式,多路复用模型从流程上和同步阻塞的区别不大,主要区别在于操作系统为用户提供了同时轮询多个IO句柄来查看是否有IO事件的接口(如select),这从根本上允许用户可以使用单个线程来管理多个IO句柄的问题。

  4. 异步IO(Asynchronous IO):即Proactor设计模式。在异步IO模型中,用户不需要去轮询IO事件,然后才进行数据的读取,处理;在异步IO模型中,IO事件就绪的时候,内核会开启一个独立的内核线程去执行执行IO操作,实现真正的异步IO。这个时候用户线程可以直接读取内核线程准备好的数据。

多路复用IO模型和异步IO模型的区别主要是用户线程得知IO事件的时候在多路复用IO模型中,用户线程需要自己去处理IO,而在异步IO模型中数据已经由内核线程为用户线程准备好了。在实际应用中,在高效的IO应用中,最常见的是第三种IO模型,异步IO目前操作系统方面的支持并不是很好而且在性能数据上并不是很好看。

上面对四种IO模型进行了极其简单的概括,如多读者意犹未尽可以在网上查阅相关资料或者和作者联系。

select、poll和epoll

JAVA对NIO的支持是从1.4版本开始的,是基于多路复用技术,而在linux操作系统方面多路复用技术有三种常用的机制:select、poll和epoll,epoll的支持也只是linux2.6版本之后才提供,java在jdk5.0的update 9之后才对epoll进行支持。这三种机制本质上都是同步IO,主要是由于他们都需要在读写事件就绪的时候需要自己进行读写,也就是这个这个读写过程是阻塞的。下面对着三种机制进行简单总结:

  1. select函数:改函数允许进程指示内核等待多个事件中的任何一个发生的时候或者在一定时间之后被唤醒,select有个致命的缺点即在多路复用中文件描述符的数量有限制,如果需要突破限制需要重新编译操作系统内核。

  2. poll函数:poll机制与select机制类似,区别是poll没有最大描述符限制。

  3. epoll函数:epoll在linux2.6内核中被提出来,是之前的select和poll的增强版本。epoll也没有文件描述符数量限制,而且是用一个文件描述符来管理多个描述符。在性能上相比上面两种有了很大的优化。

关于select、poll和epoll的详细介绍可以参考这里

JAVA NIO

JAVA的NIO是基于IO多路复用模型,在不同平台上有不同的实现方式。Linux下面用的是poll和epoll,在BSD上用kqueue,在Windows上是重叠I/O。

在JAVA NIO中有三个核心的组件:Channels、Buffers和Selectors。

JAVA NIO核心组件

在JAVA NIO中,基本上所有的IO都是从Channel开始的,读取操作即从Channel读到Buffer,写操作即从Buffer写入Channel。

NIO读写示意图


Channel

在网络IO方面,Channel的主要实现是ServerSocketChannel和SocketChannel。他们都代表一个面向流的可监听读写事件的socket。ServerSocketChannel是用于服务器端的socket,他提供了一个静态工具方法open来为用户提供获取Channel的工具:

public static ServerSocketChannel open() throws IOException {
    return SelectorProvider.provider().openServerSocketChannel();
}

其中涉及到的SelectorProvider用于创建具体的Channel,SelectorProvider的获取有三种途径,首先从系统属性中获取key为java.nio.channels.spi.SelectorProvider的值,如果没有则基于SPI机制来获取,如果再没有则最后提供默认的,这个默认值跟操作系统平台相关,比如我的mac系统,JDK提供的默认Provider是KQueueSelectorProvider。

ServerSocketChannel提供的接口

ServerSocketChannel的使用方式是面向服务器端的,一般的开发流程是:

  1. 获取一个ServerSocketChannel。

  2. 设置网络操作,这些参数主要是和TCP协议有关。

  3. 将ServerSocketChannel注册到Selector(多路复用器)。

  4. 将ServerSocketChannel和某个具体的地址绑定。

  5. 用户像多路复用器设置感兴趣的IO事件。

  6. 用户线程以阻塞或非阻塞方式轮询Selector来查看是否有就绪的IO事件。

  7. 用户针对不同的IO事件对Channel进行具体的IO操作。

SocketChannel主要是面向客户端的开发的,也是以open方式获取channel,客户端的开发流程大致如下:

  1. 获取一个SocketChannel。

  2. 设置Channel为非阻塞方式。

  3. 获取Selector。

  4. 将channel注册到Selector,并监听CONNECT事件。

  5. 调用channel的connect方法连接指定的服务器和端口。

  6. 如果连接成功则进行IO操作,如果没成功则轮询Selector处理CONNECT事件。


Selector

Selector是JAVA NIO中的多路复用器,配合SelectionKey使用,SelectionKey代表着一个Channel和Selector的关系的抽象,Channel向Selector注册的时候产生,由Selector维护。Selector维护着三个SelectionKey的集合:

key set:这个集合包含所有向Selector注册的Channel产生的SelectionKey,这个集合中的SelectionKey是不能直接被修改的,除非SelectionKey被channel,并且发生select的时候SelectionKey才被移出。

selected key set:这个集合是key set集合的子集,当有SelectionKey关联的Channel有Channel向Selector注册的IO事件就绪的时候并且有select操作,对应的SelectionKey会被放到selected key set中。因为这个集合中的SelectionKey可以通过直接调用Set的remove将SelectionKey移除。

cancelled-key:这个集合是也是key set的子集。当有已经向Selector注册的Channel发生degistered的时候,SelectionKey将被放到这个集合,并且在下一次select的时候被从所有的集合中移出。

三种集合的流转我画个图表示一下:

Selector的Selection Key集合流转图

在开发过程中,我们可以将多个Channel注册到一个Selector实例中,用一个线程来处理所有的IO事件,我们也可以将多个Channel注册到多个Selector实例中,结合高效的线程模型可以达到很好的效果。


ByteBuffer

JAVA NIO直接和Channel打交道的Buffer是ByteBuffer,ByteBuffer接口提供主要的内存分配、IO读写等相关接口。值得注意的是JAVA NIO提供了两种Buffer内存分配机制,一种是堆内存,另一种是直接内存,主要区别:

  1. 堆内存分配和回收比较快,但是网络数据需要从内核copy到堆中。

  2. 直接内存分配和回收比较慢,但是免去了从内核copy到堆中的一次copy。

这两种内存各有千秋,使用的时候要根据实际情况去选择。


总结:

这篇文章主要介绍一下JAVA NIO涉及到的一些基础概念以及JAVA提供的NIO接口进行简单介绍,JAVA NIO提供的接口使用起来,略复杂,实际项目中不建议直接使用JDK提供的API进行开发。Netty是一个基于JAVA NIO开发的可靠的JAVA NIO工具,Netty的精粹我认为除了IO模型之外还有下面的几个部分:

  1. 高效的线程模型

  2. 内存池技术

  3. 零copy技术

Netty是一个优秀的开源NIO框架,我们可以使用它来快速构建高性能的IO服务器,后面我会通过继续深入学习和大家一起分享Netty的实现和原理。



作者:陆晨

时间:除夕

© 著作权归作者所有

Float_Luuu
粉丝 221
博文 47
码字总数 104674
作品 0
长宁
高级程序员
私信 提问
加载中

评论(3)

Xsank
Xsank
除夕都这么拼,点赞~
君枫
君枫
不错
clarchen
clarchen
好文,值得学习
少啰嗦!一分钟带你读懂Java的NIO和经典IO的区别

本文引用了“架构师社区”公众号的《史上讲的最好的Java NIO与IO的区别与应用》一文部分内容,感谢原作者的技术分享。 1、引言 很多初涉网络编程的程序员,在研究Java NIO(即异步IO)和经典...

JackJiang2011
06/25
0
0
Qzone 微信 Java高级——dubbo源码分析之远程通信 netty

Java高级——dubbo源码分析之远程通信 netty dubbo 底层通信选择了 netty 这个 nio 框架做为默认的网络通信框架并且通过自定义协议进行通信。dubbo 支持以下网络通信框架: Netty(默认) Min...

Java架构师那些事
2018/08/29
0
0
分别使用Java IO、NIO、Netty实现的一个Echo Server示例

分别使用Java IO、Java NIO、Netty来实现一个简单的EchoServer(即原样返回客户端的输入信息)。 Java IO int port = 9000;ServerSocket ss = new ServerSocket(port);while (true) {final S...

zgw06629
2015/05/24
2.2K
0
Java NIO框架Netty教程(一) – Hello Netty

先啰嗦两句,如果你还不知道Netty是做什么的能做什么。那可以先简单的搜索了解一下。我只能说Netty是一个NIO的框架,可以用于开发分布式的Java程序。具体能做什么,各位可以尽量发挥想象。技...

forever_coder
2014/01/23
544
0
Netty5源码分析--4.总结

JAVA NIO 复习 请先参考我之前的博文JAVA学习笔记--3.Network IO的 NIO(NonBlocking IO) SOCKET 章节。这里主要讲下JAVA NIO其中几个比较被忽略的细节,不求全,欢迎补充。 API Select 当调...

geecoodeer
2014/01/17
4.9K
18

没有更多内容

加载失败,请刷新页面

加载更多

Java Enum 底层原理

public enum WeekDay{ Mon("Monday"), Tue("Tuesday"), Wed("Wednesday"), Thu("Thursday"), Fri( "Friday"), Sat("Saturday"), Sun("Sunday"); private final String day; ......

MtrS
32分钟前
3
0
Python入门项目之锁屏单词壁纸,教你节约时间学习英语哦

很多人认为学python或者其他的编程语言,一定需要不错的英语,真的是这样吗? 不知道,但我知道怎么用python实现电脑锁屏单词壁纸,这对于每天都要多次面对锁屏屏幕的人来说,可以帮你节约每...

这人就爱编程
38分钟前
6
0
EasyPoi教程和使用案例

EasyPoi教程和使用案例 先上文档:http://easypoi.mydoc.io/ 基于Apache poi 开发的EasyPoi,比起poi更加简单易用,但是功能没有poi强大,。 特性总结: 优点: 通过简单的注解和模板 语言(熟...

Koro-Tong
38分钟前
4
0
ZetCode 绘图教程

来源:ApacheCN ZetCode 翻译项目 译者:飞龙 协议:CC BY-NC-SA 4.0 贡献指南 本项目需要校对,欢迎大家提交 Pull Request。 请您勇敢地去翻译和改进翻译。虽然我们追求卓越,但我们并不要求...

ApacheCN_飞龙
52分钟前
5
0
==和equals的区别是什么?

== 是关系运算符,equals() 是方法,结果都返回布尔值 Object 的 == 和 equals() 比较的都是地址,作用相同 == 作用: 基本类型,比较值 引用类型,比较内存地址 不能比较没有父子关系的两个...

ConstXiong
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部