现在优秀的开源项目很是多,仅在 Java 服务器端开发领域,优秀的开源项目就不胜枚举。好比从十年前就开始流行到如今依旧十分活跃的 Spring Framework,现在已经发展为一个覆盖服务器端、大数据等多个领域的平台级开源项目。还有早年间的 MVC 框架、ORM 框架,到如今涉及各个服务器开发领域的开源技术。java
但此次我要介绍的是 Netty 这个网络 IO 框架,而非 Spring 这样的流行项目。缘由在于,Netty 这样的框架所实现的功能相比 Spring Framework 来讲来讲更为基础。由于对于服务器端开发来讲,Spring Framework 核心的 IoC 和 AOP 技术其实并非必须的。固然没有这两个技术,开发复杂的项目会很困难,但这二者只是充分而非必要的条件。但 Netty 这样的技术其实对于服务器端开发来讲是必须的。程序员
从产品的角度讲,没有像 Netty 这样的网络 IO 框架,就不会有优秀的 Web 服务器、应用服务器等等的平台,而没有这些平台,仅靠 IoC 和 AOP 等技术是不可能产生优秀的软件产品。一个服务器端软件,即使没有直接使用 Netty,那每每也是间接地使用了 Netty 这样的框架。因此 Netty 这样的框架对于一个优秀的服务器端项目或产品来讲十分重要。编程
同时,由于 Netty 不只被普遍地直接使用,还被不少优秀的技术,例如 Thrift、Storm、Spark 等采用,做为其基础的 IO 层的实现技术。因此,深刻了解 Netty 还能够帮助开发人员更好地使用这些技术,在遇到一些技术问题时也能够从更深地角度去分析。设计模式
从程序员学习的角度讲,像 Netty 这样的网络 IO 框架,它们大量使用了 IO 和并发这两个技术。而这两个技术,对于优秀的服务器端开发人员来讲是必备的。经过深刻了解 Netty 的实现原理,能让程序员从实践的角度去学习优秀的 IO 和并发设计。除了 IO 和并发这两个对于服务器端开发很是重要的技术,了解 Netty 的实现原理还可让程序员去学习如何设计实现一个复杂的框架,学习到设计模式、数据结构等的应用技巧。因此,深刻了解 Netty 对于提升很是有帮助。服务器
从工做的角度讲,国内外的不少公司也一样普遍使用 Netty,好比Twitter、Facebook、华为、阿里巴巴等等。因此在平常工做中也很容易接触到 Netty 的使用。网络
此外,Netty 的文档相对 Spring 这样的技术来讲仍是偏匮乏。同时,Netty 3/4/5 这几个版本的变化仍是很是大的,尤为是从 3 到 4。这给使用者带来了不少的问题,而深刻了解 Netty 会帮助使用者解决这些问题。数据结构
因此,总结一下,深刻了解 Netty 的好处有:多线程
Java NIO 对于 Netty 来讲是基础技术(Java NIO 是基于 Linux epoll 等技术),因此下面将介绍一下 Java NIO 方面的基础知识。并发
对于并发技术,由于它相比较 Java NIO 来讲在工做中被直接使用到的机会要多得多,因此这里就不作介绍了。可是后续会介绍 Netty 中的并发设计。框架
Selector selector = null; ServerSocketChannel server = null; try { selector = Selector.open(); // 打开 Selector server = ServerSocketChannel.open(); // 打开 ServerSocketChannel server.socket().bind(new InetSocketAddress(port)); // 绑定端口 server.configureBlocking(false); // 设置为非阻塞模式 server.register(selector, SelectionKey.OP_ACCEPT); // 将 ServerSocketChannel 注册到 Selector 上 while (true) { selector.select(); for (Iterator<SelectionKey> i = selector.selectedKeys().iterator(); i.hasNext();) { SelectionKey key = i.next(); i.remove(); if (key.isConnectable()) { ((SocketChannel)key.channel()).finishConnect(); } if (key.isAcceptable()) { // accept connection SocketChannel client = server.accept(); // 接受 TCP 链接 client.configureBlocking(false); client.socket().setTcpNoDelay(true); client.register(selector, SelectionKey.OP_READ); // 将 SocketChannel 注册到 Selector 上 } if (key.isReadable()) { // ...read messages... } } } } catch (Throwable e) { throw new RuntimeException("Server failure: "+e.getMessage()); } finally { try { selector.close(); server.socket().close(); server.close(); stopped(); } catch (Exception e) { // do nothing - server failed } }
上面这段代码介绍了 Java NIO 的基本使用方式。好比,ServerSocketChannel
和 SocketChannel
要注册在 Selector
上,而且这两个 SelectableChannel
要经过 configureBlocking(boolean block)
方法将其设置为非阻塞模式;经过不断轮询 Selector
的方式,经过 Selector.selectedKeys()
得到例如客户端链接、读写消息等事件,并作相应处理。由于在一个 Selector
上能够注册多个 SelectableChannel
,因此实现了一个线程处理多个链接的目的。
上面就是 Java NIO 的大体的使用方法。
Java 这样编程语言级的 IO 如同线程同样,最终仍是基于操做系统的实现支持。在 Linux 中,是靠 select、poll 和 epoll 等技术支持的。
在非阻塞 IO 出现以前,咱们是用阻塞的方式使用 IO。当一个流(文件、套接字等)没法读写的时候,操做便被阻塞,线程被挂起。因而,对于一个流的操做,必须独占一个线程。虽然能够用多进程或多线程的方式去并发处理,可是这样所带来的大量的资源使用、线程上下文的切换,都使得这种方式十分低效。因此便出现了非阻塞 IO。
最早出现的非阻塞 IO 技术是 select 和 poll。它们的原理简单来讲不断地轮询多个流,看是否有流能够被操做,从而实现了非阻塞的 IO 操做。
但这两种方式也有明显的缺点。其缺点在于,select 和 poll 对流的轮询的方式很“傻”。它俩在轮询时并不会区分一个流上是否真的有数据,而是一股脑地轮询全部的流。所以这二者的性能会随着流的增长而线性降低。
epoll 的意思是 event poll。epoll 相较 select 和 poll 的改进在于不像前者去轮询全部的流,而是只去轮询有实际事件发生的流。这一点的实现是基于硬件中断实现的。
Channel
表明了 IO 操做的通道。Channel
经过后面提到的 Buffer
进行数据的读写。Channel
有一类咱们后面会常常提到的 SelectableChannel
。配合 Selector
,可以实现非阻塞 IO。后续将被常常提到的 ServerSocketChannel
和 SocketChannel
都是 SelectableChannel
的子类。
选择器,也可被称为多路复用器,是实现非阻塞 IO 的关键。经过调用 Selector.open()
,即可获得一个当前操做系统下的默认 Selector
实现。经过 SelectorProvider
修改这一行为,自定义实现。
SelectableChannel
能够经过 register(Selector, int)
方法将本身注册在 Selector
上,并提供其所关注的事件类型。
经过地调用 Selector
的 select()
(阻塞)或 selectNow()
(非阻塞)方法,Selector
会将从上次 select()
方法调用以后的全部就绪状态通道上的 SelectionKey
放入一个集合中。同时,select()
方法所返回的 int 值表明了有多少 Channel 自上次以后便为就绪状态。
在调用 select()
以后,经过调用 Selector
的 selectedKeys()
获得就绪状态通道的 SelectionKey
。经过遍历这一集合,再经过 SelectionKey
即可获得全部就绪状态的 SelectableChannel
,进一步即可以作相应的操做。
Java NIO 以 Buffer
最为数据传输的载体。经过使用 Buffer
,Java NIO 提升了数据传输的效率。而 Netty 的不少改进也是围绕 Buffer
进行的,好比 Buffer
的池化。
后面会提到的 SocketChannel
,其使用了 Buffer
的一个子类 ByteBuffer
来进行数据的读写。
接下来会介绍 Netty 中一些主要的类,以及它们的做用以及关系。