Netty 源码分析(一):引言和 Java NIO 介绍

为什么要介绍 Netty

如今优秀的开源项目非常多,仅在 Java 服务器端开发领域,优秀的开源项目就不胜枚举。比如从十年前就开始流行到现在依旧十分活跃的 Spring Framework,如今已经发展为一个覆盖服务器端、大数据等多个领域的平台级开源项目。还有早年间的 MVC 框架、ORM 框架,到现在涉及各个服务器开发领域的开源技术。

但这次我要介绍的是 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 的好处有:

  1. 提高程序员在网络 IO 和并发,以及设计模式、数据结构等领域的内功
  2. 学习如何设计复杂的框架
  3. 帮助解决使用 Netty 以及将 Netty 作为基础的技术时遇到的问题

目录

  • Java NIO 介绍
  • Netty 主要组件和类图
  • 源码分析之 Server Side Channel Init and Register
  • 源码分析之 Server Side Channel Port Bind
  • 源码分析之 Server Side Accept Client Connect
  • 源码分析之 Read Message
  • 源码分析之 Write Message
  • Netty 线程模型

Java NIO 介绍

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 的基本使用方式。比如,ServerSocketChannelSocketChannel 要注册在 Selector 上,并且这两个 SelectableChannel 要通过 configureBlocking(boolean block) 方法将其设置为非阻塞模式;通过不断轮询 Selector 的方式,通过 Selector.selectedKeys() 获得例如客户端连接、读写消息等事件,并做相应处理。因为在一个 Selector 上可以注册多个 SelectableChannel,所以实现了一个线程处理多个连接的目的。

上面就是 Java NIO 的大致的使用方法。

技术基础

Java 这样编程语言级的 IO 如同线程一样,最终还是基于操作系统的实现支持。在 Linux 中,是靠 select、poll 和 epoll 等技术支持的。

select(poll 类似)

在非阻塞 IO 出现之前,我们是用阻塞的方式使用 IO。当一个流(文件、套接字等)无法读写的时候,操作便被阻塞,线程被挂起。于是,对于一个流的操作,必须独占一个线程。虽然可以用多进程或多线程的方式去并发处理,但是这样所带来的大量的资源使用、线程上下文的切换,都使得这种方式十分低效。所以便出现了非阻塞 IO。

最先出现的非阻塞 IO 技术是 select 和 poll。它们的原理简单来说不断地轮询多个流,看是否有流可以被操作,从而实现了非阻塞的 IO 操作。

但这两种方式也有明显的缺点。其缺点在于,select 和 poll 对流的轮询的方式很“傻”。它俩在轮询时并不会区分一个流上是否真的有数据,而是一股脑地轮询所有的流。因此这两者的性能会随着流的增加而线性下降。

epoll

epoll 的意思是 event poll。epoll 相较 select 和 poll 的改进在于不像前者去轮询所有的流,而是只去轮询有实际事件发生的流。这一点的实现是基于硬件中断实现的。

组成介绍

selector-channel

Channel

Channel 代表了 IO 操作的通道。Channel 通过后面提到的 Buffer 进行数据的读写。Channel 有一类我们后面会经常提到的 SelectableChannel。配合 Selector,能够实现非阻塞 IO。后续将被经常提到的 ServerSocketChannelSocketChannel 都是 SelectableChannel 的子类。

Selector

选择器,也可被称为多路复用器,是实现非阻塞 IO 的关键。通过调用 Selector.open(),便可得到一个当前操作系统下的默认 Selector 实现。通过 SelectorProvider 修改这一行为,自定义实现。

SelectableChannel 可以通过 register(Selector, int) 方法将自己注册在 Selector 上,并提供其所关注的事件类型。

通过地调用 Selectorselect()(阻塞)或 selectNow()(非阻塞)方法,Selector 会将从上次 select() 方法调用之后的所有就绪状态通道上的 SelectionKey 放入一个集合中。同时,select() 方法所返回的 int 值代表了有多少 Channel 自上次之后便为就绪状态。

在调用 select() 之后,通过调用 SelectorselectedKeys() 得到就绪状态通道的 SelectionKey。通过遍历这一集合,再通过 SelectionKey 便可得到所有就绪状态的 SelectableChannel,进一步便可以做相应的操作。

Buffer

Java NIO 以 Buffer 最为数据传输的载体。通过使用 Buffer,Java NIO 提高了数据传输的效率。而 Netty 的很多改进也是围绕 Buffer 进行的,比如 Buffer 的池化。

后面会提到的 SocketChannel,其使用了 Buffer 的一个子类 ByteBuffer 来进行数据的读写。

接下来

接下来会介绍 Netty 中一些主要的类,以及它们的作用以及关系。

转载于:https://my.oschina.net/lifany/blog/513107

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值