一、Netty简介

1.1、Netty概述

Netty 是一个基于nio的客户、服务器端编程框架,Netty提供异步的,事件驱动的网络应用程序框架和工具,可以快速开发高可用的客户端和服务器。Netty是基于nio的,它封装了jdk的nio,让我们使用起来更加方便灵活。

Netty是由jboss提供的一款开源框架,常用于搭建RPC中的TCP服务器、WebSocket服务器,甚至是类似Tomcat的web服务器,反正就是各种网络服务器,在处理高并发的项目中,功能丰富且性能良好,基于Java中NIO的二次封装,具有比原生NIO更好更稳健的体验。Dubbo底层就是用Netty实现的。

 官网给出的Netty底层示意图:(Netty底层用到了零拷贝技术)

android Netty 框架 netty框架入门_Netty功能特性

1.2、为什么使用Netty?

我们已经有了NIO能够提高程序效率了,但在实际的网络开发中,其实很少使用 Java NIO 原生的 API,为什么还要使用Netty?

主要有以下原因:

  • 原生 API 使用单线程模型,不能很好利用多核优势,如果自己去写多线程结合起来比较麻烦;
  • 原生 API 是直接使用的 IO 数据,没有做任何封装处理,对数据的编解码、TCP 的粘包和拆包、客户端断连、网络的可靠性和安全性方面没有做处理;

在《Netty 权威指南》这本书里提到一个真实的故事,两个项目团队都要做基于 NIO 非阻塞特性的一个高性能、异步和高可靠性的底层通信框架,但一个团队选择了基于 Java NIO API 从头开发,另一个团队选择了基于 Netty 开发。最终,从头开发的团队遇到了各种各样的问题,导致项目延迟,而基于 Netty 开发的团队则进展顺利。

其实网络开发是一个比较复杂的事情,因为网络的不稳定性,通常会遇到各种各样的问题,比如前面提到的客户端突然断连、TCP 的拆包和沾包等等。

幸运的是,网络上已经有了这么一个成熟的框架帮我们处理了这些事情,这个框架就是 Netty。

下面是使用Netty不使用JDK原生NIO的一些具体原因:

  • 使用JDK自带的NIO需要了解太多的概念,编程复杂
  • Netty底层IO模型随意切换,而这一切只需要做微小的改动,就可以直接从NIO模型变身为IO模型
  • Netty自带的拆包解包,异常检测等机制,可以从NIO的繁重细节中脱离出来,只需要关心业务逻辑
  • Netty解决了JDK的很多包括空轮询在内的bug
  • Netty底层对线程,selector做了很多细小的优化,精心设计的线程模型做到非常高效的并发处理
  • 自带各种协议栈让你处理任何一种通用协议都几乎不用亲自动手
  • Netty社区活跃,遇到问题随时邮件列表或者issue
  • Netty已经历各大rpc框架,消息中间件,分布式通信中间件线上的广泛验证,健壮性无比强大

简单的说:Netty封装了JDK的NIO,让你用得更爽,你不用再写一大堆复杂的代码。并且还提供了更多牛叉的开箱即用功能。

1.3、Netty 主要应用场景

官方术语:Netty是一个基于NIO的异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。

高性能的 RPC 框架:常用于微服务之间的高性能远程调用(如 Dubbo)

游戏行业:Netty 可以很轻松地定制和开发一个私有协议栈,

即时通讯:Netty 基于 Java NIO,并且做了一些优化,支持高性能的即时通讯

1.4、Netty的事件驱动

例如很多系统都会提供 onClick() 事件,这个事件就代表鼠标按下事件。事件驱动模型的大体思路如下:

  • 有一个事件队列;
  • 鼠标按下时,往事件队列中增加一个点击事件;
  • 有个事件泵,不断循环从队列取出事件,根据不同的事件,调用不同的函数;
  • 事件一般都各自保存各自的处理方法的引用。这样,每个事件都能找到对应的处理方法;

android Netty 框架 netty框架入门_Netty功能特性_02

 为什么使用事件驱动?

  • 程序中的任务可以并行执行
  • 任务之间高度独立,彼此之间不需要互相等待
  • 在等待的事件到来之前,任务不会阻塞

Netty使用事件驱动的方式作为底层架构,包括:

  • 事件队列(event queue):接收事件的入口。
  • 分发器(event mediator):将不同的事件分发到不同的业务逻辑单元。
  • 事件通道(event channel):分发器与处理器之间的联系渠道。
  • 事件处理器(event processor):实现业务逻辑,处理完成后会发出事件,触发下一步操作。

android Netty 框架 netty框架入门_Netty的应用场景_03


二、Reactor 线程模型

首先介绍处理事件的两种方式:

  • 轮询方式:线程不断轮询访问相关事件发生源有没有发生事件,有发生事件就调用事件处理逻辑。Java 原生的 NIO 就是使用的轮询方式。
  • 事件驱动方式,发生事件,主线程把事件放入事件队列,在另外线程不断循环消费事件列表中的事件,调用事件对应的处理逻辑处理事件。事件驱动方式也被称为消息通知方式,其实是设计模式中观察者模式的思路。

Reactor 是反应堆的意思。Reactor 线程模型是指通过一个或多个输入,同时传递给服务处理器的服务请求的事件驱动处理模式。

Reactor 模式主要工作原理如下图:

android Netty 框架 netty框架入门_Netty核心组件_04

 Reactor 有一个专门负责监听和分发事件的线程,如图中的 Service Handler,所有请求进来后,被它分发到具体的处理线程,如图中的 Event Handler 去处理。

Reactor 可能有多个,而 Netty 正是使用了多 Reactor 的线程模型。


三、Netty可以做什么

Netty 的功能特性图:

android Netty 框架 netty框架入门_android Netty 框架_05

Netty Core 提供了基本功能,包括文件零拷贝、基本的 API、可扩展的基于事件的模型(下文详细介绍)。

Netty 支持非常多的协议,比如 HTTP、WebSocket 等。当然,Netty 也可以自定义协议。常见协议的示例代码可以参考 netty 源码里面的 example 包。

Netty 同时支持 Java 的 BIO 和 NIO 两种方式。且很容易与 Spring 等主流框架进行集成。


四、Netty核心组件

android Netty 框架 netty框架入门_Netty核心组件_06

Netty框架包含如下的组件:

  • ServerBootstrap :用于接受客户端的连接以及为已接受的连接创建子通道,一般用于服务端。
  • Bootstrap:不接受新的连接,并且是在父通道类完成一些操作,一般用于客户端的。
  • Channel:对网络套接字的I/O操作,例如读、写、连接、绑定等操作进行适配和封装的组件。
  • EventLoop:处理所有注册其上的channel的I/O操作。通常情况一个EventLoop可为多个channel提供服务。
  • EventLoopGroup:包含有多个EventLoop的实例,用来管理 event Loop的组件,可以理解为一个线程池,内部维护了一组线程。
  • ChannelHandler和ChannelPipeline:例如一个流水线车间,当组件从流水线头部进入,穿越流水线,流水线上的工人按顺序对组件进行加工,到达流水线尾部时商品组装完成。流水线相当于ChannelPipeline,流水线工人相当于ChannelHandler,源头的组件当做event。
  • ChannelInitializer:用于对刚创建的channel进行初始化,将ChannelHandler添加到channel的ChannelPipeline处理链路中。
  • ChannelFuture:与jdk中线程的Future接口类似,即实现并行处理的效果。可以在操作执行成功或失败时自动触发监听器中的事件处理方法。

上面的Netty框架包含如下的组件大概看的有点蒙,我们用Netty实现一个客户端与服务端通信的程序来举例:

(1) 添加Netty依赖

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.5.Final</version>
</dependency>

(2) 服务端:

public class NettyServer {
    public static void main(String[] args) {
        // 用于接受客户端的连接以及为已接受的连接创建子通道,一般用于服务端。
        ServerBootstrap serverBootstrap = new ServerBootstrap();

        // EventLoopGroup包含有多个EventLoop的实例,用来管理event Loop的组件
        // 接受新连接线程
        NioEventLoopGroup boos = new NioEventLoopGroup();
        // 读取数据的线程
        NioEventLoopGroup worker = new NioEventLoopGroup();

        //服务端执行
        serverBootstrap
                .group(boos, worker)
                // Channel对网络套接字的I/O操作,
                // 例如读、写、连接、绑定等操作进行适配和封装的组件。
                .channel(NioServerSocketChannel.class)
                // ChannelInitializer用于对刚创建的channel进行初始化
                // 将ChannelHandler添加到channel的ChannelPipeline处理链路中。
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    protected void initChannel(NioSocketChannel ch) {
                        // 组件从流水线头部进入,流水线上的工人按顺序对组件进行加工
                        // 流水线相当于ChannelPipeline
                        // 流水线工人相当于ChannelHandler
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
                            //这个工人有点麻烦,需要我们告诉他干啥事
                            @Override
                            protected void channelRead0(ChannelHandlerContext ctx, String msg) {
                                System.out.println(msg);
                            }
                        });
                    }
                })
                .bind(8000);
    }
}

(3)客户端:

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        // 不接受新的连接,并且是在父通道类完成一些操作,一般用于客户端的。
        Bootstrap bootstrap = new Bootstrap();

        // EventLoopGroup包含有多个EventLoop的实例,用来管理event Loop的组件
        NioEventLoopGroup group = new NioEventLoopGroup();

        //客户端执行
        bootstrap.group(group)
                // Channel对网络套接字的I/O操作,
                // 例如读、写、连接、绑定等操作进行适配和封装的组件。
                .channel(NioSocketChannel.class)
                // 用于对刚创建的channel进行初始化,
                // 将ChannelHandler添加到channel的ChannelPipeline处理链路中。
                .handler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel ch) {
                        // 组件从流水线头部进入,流水线上的工人按顺序对组件进行加工
                        // 流水线相当于ChannelPipeline
                        // 流水线工人相当于ChannelHandler
                        ch.pipeline().addLast(new StringEncoder());
                    }
                });

        //客户端连接服务端
        Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();

        while (true) {
            // 客户端使用writeAndFlush方法向服务端发送数据,返回的是ChannelFuture
            // 与jdk中线程的Future接口类似,即实现并行处理的效果
            // 可以在操作执行成功或失败时自动触发监听器中的事件处理方法。
            ChannelFuture future = channel.writeAndFlush("测试数据");
            Thread.sleep(2000);
        }
    }
}