本文是Netty系列第4篇git
上一篇文章咱们深刻了解了I/O多路复用的三种实现形式,select/poll/epoll。github
那Netty是使用哪一种实现的I/O多路复用呢?这个问题,得从Java NIO包提及。面试
Netty实际上也是一个封装好的框架,它的网络I/O本质上仍是使用了Java的NIO包(New IO,不是网络I/O模型的NIO,Nonblocking IO)包。因此,从网络I/O模型到Netty,咱们还须要了解下Java NIO包。编程
本文预计阅读时间 5 分钟,将重点回答如下几个问题:设计模式
上一篇文章咱们已经了解了I/O多路复用的实现形式。
就是多个的进程的IO能够注册到一个复用器(selector)上,而后用一个进程调用select,select会监听全部注册进来的IO。浏览器
NIO包作了对应的实现。以下图所示。性能优化
有一个统一的selector负责监听全部的Channel。这些channel中只要有一个有IO动做,就能够经过Selector.select()方法检测到,而且使用selectedKeys获得这些有IO的channel,而后对它们调用相应的IO操做。微信
咱们来个简单的demo作一下演示。如何使用NIO中三个核心组件(Buffer缓冲区、Channel通道、Selector选择器)来编写一个服务端程序。网络
public class NioDemo { public static void main(String[] args) { try { //1.建立channel ServerSocketChannel socketChannel1 = ServerSocketChannel.open(); //设置为非阻塞模式,默认是阻塞的 socketChannel1.configureBlocking(false); socketChannel1.socket().bind(new InetSocketAddress("127.0.0.1", 8811)); ServerSocketChannel socketChannel2 = ServerSocketChannel.open(); socketChannel2.configureBlocking(false); socketChannel2.socket().bind(new InetSocketAddress("127.0.0.1", 8822)); //2.建立selector,并将channel1和channel2进行注册。 Selector selector = Selector.open(); socketChannel1.register(selector, SelectionKey.OP_ACCEPT); socketChannel2.register(selector, SelectionKey.OP_ACCEPT); while (true) { //3.一直阻塞直到有至少有一个通道准备就绪 int readChannelCount = selector.select(); Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); //4.轮训已经就绪的通道 while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); //5.判断准备就绪的事件类型,并做相应处理 if (key.isAcceptable()) { // 建立新的链接,而且把链接注册到selector上,而且声明这个channel只对读操做感兴趣。 ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel(); SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); } if (key.isReadable()) { SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer readBuff = ByteBuffer.allocate(1024); socketChannel.read(readBuff); readBuff.flip(); System.out.println("received : " + new String(readBuff.array())); socketChannel.close(); } } } } catch (IOException e) { e.printStackTrace(); } } }
经过这个代码示例,咱们能清楚地了解如何用Java NIO包实现一个服务端:多线程
程序启动后,会一直阻塞在selector.select()。
经过浏览器调用localhost:8811 或者 localhost:8822就能触发咱们的服务端代码了。
上文演示的Java NIO服务端已经比较清楚地展现了使用NIO编写服务端程序的过程。
那这个过程当中如何实现了I/O多路复用的呢?
咱们得深刻看下selector的实现。
//2.建立selector,并将channel1和channel2进行注册。 Selector selector = Selector.open();
从open这里开始吧。
这里用了一个SelectorProvider来建立selector。
进入SelectorProvider.provider(),看到具体的provider是由
sun.nio.ch.DefaultSelectorProvider建立的,对应的方法是:
咦?原来不一样的操做系统会提供不一样的provider对象。这里包括了PollSelectorProvider、EPollSelectorProvide等。
名字是否是有点眼熟?
没错,跟咱们上一篇文章分析过的I/O多路复用的不一样实现方式poll/epoll有关。
咱们选择默认的
sun.nio.ch.PollSelectorProvider往下看看。
OK,找到了实现类PollSelectorImpl。
而后,经过如下调用:
找到最终的native方法poll0。
是否是仍然很眼熟?
没错!跟咱们上一篇文章分析过的poll函数是一致的。
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
绕了这么久,到最后,仍是找到了咱们聊过I/O多路复用的 poll 实现。
至此,咱们终于把Java NIO和 I/O多路复用模型串联起来了。
Java NIO包使用selector,实现了I/O多路复用模型。
同时,在不一样的操做系统中,会有不一样的poll/epoll选择。
那既然已经有了NIO包了,咱们能够本身手动编写服务框架了,为何还须要封装一个Netty框架呢?有什么好处呢?
好处固然是有不少了!咱们从一开始实现的demo提及。
咱们的demo确实已经可以工做了,可是仍是有比较明显的问题。第4步(轮询已经就绪的通道)和第5步(对事件做相应处理)是在同一个线程中的,当事件处理比较耗时甚至阻塞时,整个流程就会阻塞了。
咱们使用的实际上就是 “单Reactor单线程” 设计模式。
这种模型在Reactor中负责监听端口、接收请求,若是是链接事件交给acceptor处理,若是是读写事件和业务处理就交给handler处理,但始终只有一个线程执行全部的事情。
为了提升性能,咱们理所固然至关能够把事件处理交给线程池,那就能够演进为 “单Reactor多线程” 设计模式。
这种模型和第一种模型的主要区别是把业务处理从以前的单一线程脱离出来,换成线程池处理。Reactor线程只处理链接事件、读写事件,全部业务处理都交给线程池,充分利用多核机器的资源,提升性能。
可是这仍然不够!
咱们能够发现,一个Reactor线程承担了全部的网络事件,例如监听和响应,高并发场景下单线程存在性能问题。
为了充分利用多核能力,能够构建两个 Reactor,主 Reactor 单独监听server socket,accept新链接,而后将创建的 SocketChannel 注册给指定的从 Reactor,从Reactor再执行事件的读写、分发,把业务处理就扔给worker线程池完成。这就演进为 ”主从Reactor模式“ 设计模式。
因此,若是有人直接帮咱们 封装好这样的设计模式 ,是否是太好了?
没错,Netty就是这样的“活雷锋”!
Netty就使用了主从Reactor模式封装了Java NIO包的使用,大大提升了性能。
除了封装了高性能的设计模式外,Netty还有许多其余优势:
正是由于 Netty 作到了高性能、高稳定性、高易用性,完美弥补了 Java NIO 的不足,因此在咱们在网络编程时,首选Netty,而不是本身直接使用Java NIO。
回顾一下前几章内容,到目前为止,咱们从网络I/O模型出发,一步步了解到了Netty的网络I/O模型。
对于I/O多路复用、Java NIO包 和 Netty 的关系也有了全面的认识。
有了这些知识基础,咱们初步了解了Netty是什么,为何使用Netty。
后面的文章,咱们将逐步展开Netty框架的核心知识点,敬请期待。
都看到最后了,原创不易,点个关注,点个赞吧~
文章持续更新,能够微信搜索「阿丸笔记 」第一时间阅读,回复【笔记】获取Canal、MySQL、HBase、JAVA实战笔记,回复【资料】获取一线大厂面试资料。
知识碎片从新梳理,构建Java知识图谱: github.com/saigu/JavaK…(历史文章查阅很是方便)