Netty NIO异步框架介绍及使用

1. 简介

Netty是一个高性能、异步事件驱动的NIO框架,提供了对TCP、UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。
通常情况下解决特定场景下都有特定的协议来解决我们的需求,比如HTTP协议 、FTP协议等,但是对于一些特殊场景下,单个协议无法解决我们的需求,我们需要一个高度优化的协议来处理一些特殊的场景。例如你可能想实现一个优化了的 Ajax 的聊天应用、媒体流传输或者是大文件传输器,你甚至可以自己设计和实现一个全新的协议来准确地实现你的需求。Netty可以很好的解决我们的这些特殊需求。Netty是一个提供 asynchronous event-driven (异步事件驱动)的网络应用框架,是一个用以快速开发高性能、高可靠性协议的服务器和客户端。Netty的高性能、高可靠性得益于其优秀的哲学设计思想,netty采用Reactor模式设计,主要优势体现在:
1. 异步非阻塞通信;
2. 零拷贝;
3. 内存池;
4. 高效的Reactor线程模型;
5. 无锁化的串行设计理念;
6. 高效的并发编程;
7. 高性能的序列化框架;
8. 灵活的TCP参数配置能力。

2. 设计模型

Netty中的Reactor模型主要由多路复用器(Acceptor)、事件分发器(Dispatcher)、事件处理器(Handler)组成,可以分为三种。

(1) 单线程模型:所有I/O操作都由一个线程完成,即多路复用、事件分发和处理都是在一个Reactor线程上完成的。适用于小容量场景。
单线程模型
(2) 多线程模型:为了解决单线程模型存在的一些问题,演化而来的Reactor线程模型。在绝大多数场景下,Reactor多线程模型都可以满足性能需求。
多线程模型
(3) 主从多线程模型:采用多个reactor,每个reactor都在自己单独的线程里执行。如果是多核,则可以同时响应多个客户端的请求,一旦链路建立成功就将链路注册到负责I/O读写的SubReactor线程池上。适用于大容量场景,比如海量连接处理。
主从线程模型
事实上,Netty的线程模型并非固定不变,在启动辅助类中创建不同的EventLoopGroup实例并通过适当的参数配置,就可以支持上述三种Reactor线程模型。正是因为Netty对Reactor线程模型的支持提供了灵活的定制能力,所以可以满足不同业务场景的性能需求。

3. 逻辑架构

Netty逻辑架构如下图所示,分为Reactor通信调度层、PipeLine、业务逻辑编排层三层。
(1) Reactor通信调度层
该层的主要职责是监听网络的读写和连接操作,负责将网络层的数据读取到内存缓冲区中,然后触发各种网络事件,例如连接创建、连接激活、读事件、写事件等等。将这些事件触发到PipeLine中,由PipeLine管理的职责链来后续进行处理。
(2) PipeLine
PipeLine是职责链ChannelPipeLine,它负责事件在职责链中的有序传播,同时负责动态的编排职责链。职责链可以选择监听和处理自己关心的事件,它可以拦截处理和向后/向前传播事件。不同应用的Handler节点的功能也不同,通常情况下,往往会开发编解码Handler用于消息的编解码,它可以将外部的协议消息转换成内部的POJO对象,这样上层业务则只需要关心处理业务逻辑即可,不需要感知底层的协议差异和线程模型差异,实现层面的分层隔离。
(3)业务逻辑编排层
业务逻辑编排层通常有两类:一类是纯粹的业务逻辑编排,还有一类是其他的应用层协议插件,用于特定协议相关的会话和链路管理。

架构的不同层面,需要关心和处理的对象都不同,通常情况下,对于业务开发者,只需要关心职责链的拦截和业务Handler的编排,因为应用层协议栈往往是开发一次,到处运行,实际上对于业务开发者来说,只需要关心服务层的业务逻辑开发即可。各种应用协议以插件的形式提供,只有协议开发人员需要关注协议插件,对于其他业务开发人员来说,只需要关心业务逻辑定制即可。这种分层的架构设计理念实现了NIO框架各层之间的解耦,便于上层业务协议栈的开发和业务逻辑的定制。

4. 示例代码及API介绍

下面是一个airplay video服务程序,主要解析HTTP协议,也就是一个http服务器。

public class AirPlayServer extends Thread {

   /////////////////////////////

   ///////////////////////////

   public void run(){
      // AirPlay Video Server
      int port = AirPlay.PORT;
      EventLoopGroup videoBossGroup = new NioEventLoopGroup(1);
      EventLoopGroup videoWorkerGroup = new NioEventLoopGroup(1);
      ChannelFuture videoFuture = null;
      try {
         //////////////////////
         videoFuture = startVideoServer(port, videoBossGroup, videoWorkerGroup);
         // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
         videoFuture.channel().closeFuture().sync();
         mirrorFuture.channel().closeFuture().sync();

        } catch (InterruptedException e) {
           e.printStackTrace();

        } finally {
           videoWorkerGroup.shutdownGracefully();
            videoBossGroup.shutdownGracefully();
      }
      Log.d(TAG, "service stopped");
   }
   private ChannelFuture startVideoServer(int port, EventLoopGroup bossGroup, EventLoopGroup workerGroup) throws InterruptedException {
      // Netty - Video
      ServerBootstrap b = new ServerBootstrap();
      b.group(bossGroup, workerGroup)
      .channel(NioServerSocketChannel.class)
      .childHandler(new ChannelInitializer<Channel>() {

         @Override
            protected void initChannel(Channel ch) throws Exception {
            if (LOG_PACKET) {
               ch.pipeline().addLast("logger", new ChannelLogger("VideoChannel"));
            }
            ch.pipeline()
            .addLast("server_codec", new HttpServerCodec())
            .addLast("aggregator", new HttpObjectAggregator(512*1024))
            .addLast("reverse", new AirPlayReverseHandler(mKey))
            .addLast("airplay", new AirPlayVideoHandler())
            .addLast("airphoto", new AirPlayPhotoHandler());
            }
      })
      .option(ChannelOption.SO_BACKLOG, 128)
      .childOption(ChannelOption.SO_KEEPALIVE, true);
      // Bind and start to accept incoming connections.
      ChannelFuture f = b.bind(port).sync();
      return f;
   }

1、NioEventLoopGroup
是用来处理I/O操作的线程池,Netty对 EventLoopGroup 接口针对不同的传输协议提供了不同的实现。在本例子中,需要实例化两个NioEventLoopGroup,通常第一个称为“boss”,用来accept客户端连接,另一个称为“worker”,处理客户端数据的读写操作。

2、ServerBootstrap
是启动服务的辅助类,有关socket的参数可以通过ServerBootstrap进行设置。

3、NioServerSocketChannel
这里指定NioServerSocketChannel类初始化channel用来接受客户端请求。

扫描二维码关注公众号,回复: 1042710 查看本文章

4、ChannelPipeline
通常会为新SocketChannel通过添加一些handler,来设置ChannelPipeline。ChannelInitalizer 是一个特殊的handler,
其中initChannel方法可以为SocketChannel 的pipeline添加指定handler。这个地方采用了责任链模式设计,职责链可以选择
监听和处理自己关心的事件,它可以拦截处理和向后/向前传播事件。

5、通过绑定端口port启动服务,就可以对外提供服务了。
当然如果要实现一个client,代码结构和服务端基本类似,这里就不示例了。

6、业务逻辑处理
数据接收到后需要对这些TCP包数据做处理,那么这些业务逻辑可以用相应的Handler来处理。
业务逻辑架构

再看一个处理AirTunes的服务端的业务处理代码,这里对RTSP及RTP协议进行处理

public class RaopRtsPipelineFactory implements ChannelPipelineFactory {
    @Override
    public ChannelPipeline getPipeline() throws Exception {

        final ChannelPipeline pipeline = Channels.pipeline();
        //因为是管道 注意保持正确的顺序

        //构造executionHanlder 和关闭executionHanlder
        final AirTunesRunnable airTunesRunnable = AirTunesRunnable.getInstance();
        pipeline.addLast("exectionHandler", airTunesRunnable.getChannelExecutionHandler());
        pipeline.addLast("closeOnShutdownHandler", new SimpleChannelUpstreamHandler(){
            @Override
            public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
                airTunesRunnable.getChannelGroup().add(e.getChannel());
                super.channelOpen(ctx, e);
            }
        });

        //add exception logger
        pipeline.addLast("exceptionLogger", new ExceptionLoggingHandler());

        //rtsp decoder & encoder
        pipeline.addLast("decoder", new RtspRequestDecoder());
        pipeline.addLast("encoder", new RtspResponseEncoder());

        //rstp logger and errer response
        pipeline.addLast("logger", new RtspLoggingHandler());
        pipeline.addLast("errorResponse", new RtspErrorResponseHandler());

        //app airtunes need
        pipeline.addLast("challengeResponse", new RaopRtspChallengeResponseHandler(NetworkUtils.getInstance().getHardwareAddress()));
        pipeline.addLast("header", new RaopRtspHeaderHandler());
        //let iOS devices know server support methods
        pipeline.addLast("options", new RaopRtspOptionsHandler());

        //!!!Core handler audioHandler
        pipeline.addLast("audio", new RaopAudioHandler(airTunesRunnable.getExecutorService()));

        //unsupport Response
        pipeline.addLast("unsupportedResponse", new RtspUnsupportedResponseHandler());


        return pipeline;
    }

从上面的代码可以看到业务逻辑被分割成了多个链式结构的Handler来处理,将业务像流水线一样处理,每个过程都分工明确。

5.TCP粘包/拆包解决

对TCP协议了解的都知道TCP发过来的是没有界限的一串数据,TCP底层并不了解上层业务的逻辑,它会根据TCP缓存大小进行分包处理,所以
在业务上一个完整的包可能被TCP分成多个包发送,也有可能多个完整的包被封装成一个大包发送,Netty提供了多种Decorder来处理这种问题,
也可以自定义Decorder处理粘包/拆包问题。

业界主流的粘包解决方案:
(1). 消息定长;
(2). 包未添加回车换行符,如FTP协议;
(3). 将消息分成消息头和消息体,消息头中携带消息体长度;
(4). 更复杂的应用层协议。

FixedLengthFrameDecoder 遍历ByteBuf中的字节,判定是否有\n \r\n
DelimiterBasedFrameDecoder 1分割符标示的码流结束符
FixedLengthFrameDecoder 定长标示的码流解码器
LengthFieldBasedFrameDecoder 头信息标示长度的码流解码器
码流传输编解码技术
Java序列化存在如下缺点:无法夸语言、序列化后码流太大、序列化性能低目前业界主流的编解码框架有Google protobuf、
Facebook thrift 、JBoss Marshalling等, Netty提供了相应的接口集成相关的编解码框架。
这个开源库涉及的内容比较多,想要熟练使用相关API还需要很多工作要做,很多东西需要学习,这里只是起到抛砖引玉的
作用。网上相关的资料也比较多,如果想要深入了解可以去查找了解。

6. 参考文献

netty用户手册
https://www.gitbook.com/book/waylau/essential-netty-in-action/details
项目github源码
https://github.com/netty/netty
官方网址
http://netty.io/
电子书
Netty权威指南

猜你喜欢

转载自blog.csdn.net/u011897062/article/details/79460871