Netty核心原理与基础实战(一)备份

 1 概述

        Netty是一个Java NIO客户端/服务端框架,是一个为了快速开发可维护的高性能、高可扩展的网络服务器和客户端程序而提供的异步事件驱动基础框架和工具。基于Netty,可以快速轻松地开发网络服务器和客户端的应用程序。与直接使用Java NIO相比,Netty给大家造出了一个非常优美的轮子,它可以打打简化网络编程流程。例如,Netty极大地简化了TCP、UDP套接字和HTTP Web服务程序的开发。

        Netty的目标之一是使通信开发可以做到“快读和轻松”。使用Netty,除了能“快速和轻松”地开发TCP/UDP等自定义协议的通信程序之外,还可以做到“快速和轻松”地开发应用层协议的通信程序,如FTP/SMTP/HTTP以及其他的传统应用层协议。

        Netty的目标之二是要做到高性能、高可扩展性。基于Java的NIO,Netty设计了一套优秀的、高性能的Reactor模式实现,并且基于Netty的Reactor模式实现中的Channel(通道)、Handler(处理器)等基础类库能记性快速扩展,以支持不同协议通信、完成不同业务处理的大量应用类。

2 第一个Netty实战案例DiscardServer

        在开发介绍Netty核心原理之前,首先为大家介绍一个非常简单的入门实战案例,这是一个丢弃服务器(DiscardServer)的简单通信案例,起作用类似学习Java基础编程时的“hello,world”程序。在开发编写实战案例之前,首先准备Netty的版本,并且配置好开发环境。

2.1 创建第一个Netty项目

        首先我们需要创建项目(或者模块),取名随意(笔者取名NettyDemos)。第一个Netty的实战案例DiscardServer就在这个项目中进行实战开发。DiscardServer功能很简单:读取客户端的输入数据,直接丢弃,不给客户端任何回复。

        工具版本:JDK 1.8/Netty 4.1.6

        使用Maven导入Netty的依赖坐标到项目,坐标是:

        
            io.netty
            netty-all
            4.1.6
        

2.2 第一个Netty服务器程序

        创建一个服务端类NettyDiscardServer.java,用来实现消息的丢弃功能。

public class NettyDiscardServer {
    private  int serverPort ;
    ServerBootstrap b = new ServerBootstrap();

    public NettyDiscardServer(int serverPort) {
        this.serverPort = serverPort;
    }
    public void runServer(){
        //创建反应器轮询组
        NioEventLoopGroup bossLoopGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerLoopGroup = new NioEventLoopGroup();
        try {
            //1.设置反应器轮询组
            b.group(bossLoopGroup,workerLoopGroup);
            //2.设置NIO类型的通道
            b.channel(NioServerSocketChannel.class);
            //3.设置监听端口
            b.localAddress(serverPort);
            //4.设置通道的参数
            b.option(ChannelOption.SO_KEEPALIVE,true);
            //5.装配子通道流水线
            b.childHandler(new ChannelInitializer() {
                //有连接到达时会创建一个通道
                protected void initChannel(SocketChannel ch){
                    //流水线的职责:负责管理通道中的处理器
                    //向子通道(传输通道)流水线添加一个处理器
                    ch.pipeline().addLast(new NettyDiscardHandler());
                }
            });
            //6.开始绑定服务器
            //通过调用sync同步方法阻塞直到绑定成功
            ChannelFuture channelFuture = b.bind().sync();
            Logger.info("服务器启动成功,监听端口:",channelFuture.channel().localAddress());
            //7.等待通道关闭的异步任务结束
            //服务监听通道会一直等待通道关闭的异步任务结束
            ChannelFuture closeFuture = channelFuture.channel().closeFuture();
            closeFuture.sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //8.关闭EventLoopGroup
            workerLoopGroup.shutdownGracefully();
            bossLoopGroup.shutdownGracefully();
        }


    }

    public static void main(String[] args) {
        int potr = 8080;
        new NettyDiscardServer(potr).runServer();
    }
}

【注意:代码中的NettyDiscardHandler.java 类下面会讲到】

        如果是第一次看Netty应用程序的代码,那么上面的代码应用是晦涩难懂的,因为代码中涉及很多Netty专用组件。不过不要紧,因为Netty是基于Reactor模式实现的。通过前面的学习

1 Java 高并发编程——Reactor模式(单线程)

2 Java 高并发编程——Reactor模式(多线程)

各位应该了解Reactor模式,所以现在只需要顺藤摸瓜整理清除Netty的Reactor模式对应的组件,Netty的核心组件结构就相对简单了。

        首先要说的是Reactor模式中的Reactor组件。前面讲到,反应器组件的作用是进行IO事件的查询和分发。Netty中对应的反应器组件有很多种,不同应用通信场景用到的反应器组件各不相同。一般来说,对应于多线程的Java NIO通信的应用场景,Netty对应的反应器组件为NioEventLpoopGroup。在上面的例子中,,使用了两个NioEventLoopGroup反应器组件实例:第一个负责服务器通道新连接的IO事件的监听,可以理解为“包工头”角色,第二个主要负责传输通道的IO事件的处理和数据传输,可以理解为“工人”。

        其次要说的是Reactor模式中的Handler(处理器)角色组件。Handler的作用是对应到IO事件,完成IO事件的业务处理。Handler需要为业务做专门开发,下一小节将对上面NettyDiscardHandler自定义处理器进行介绍。

        再次,在上面的例子中还用到Netty的服务引导类ServerBootStrap。服务引导类是一个组装和集成器,职责是讲不同的Netty组件组装到一起。此外,ServerBootStrap能够按照应用场景的需要为组件设置好基础性的参数,最后帮助快速实现Netty服务器的监听和启动。服务器引导类ServerBootStrap也是本章重点之一,后面将对其进行详细介绍。

2.3 业务处理器NettyDiscardHandler

        在Reactor模式中,所有的业务处理都要在Handler中完成,业务处理一般需要自己编写,这时编写一个新类:NettyDiscardHandler.java。业务处理很简单:把收到的任何内容直接丢弃,也不回复任何信息。

       NettyDiscardHandler.java源码如下:

public class NettyDiscardHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx,Object msg){
        ByteBuf in = (ByteBuf) msg;
        try {
            Logger.info("接收到信息,丢弃如下:");
            while(in.isReadable()){
                System.out.println((char)in.readByte());
            }
            System.out.println();//换行
        }finally {
            ReferenceCountUtil.release(msg);
        }
    }

        Netty的Handler需要处理多种IO事件(如读就绪、写就绪),对应于不同的IO事件,Netty提供了一些基础方法。这些方法都已经提前封装好,应用程序直接继承或者实现即可。比如说,对于处理入站的IO事件,其对应的接口为ChannelInboundHandler,并且Netty提供了ChannelInboundHandlerAdapter适配器作为入站处理器的默认实现。

【说明】这里将引入一组新的概念:入站和出站。简单理解:入站指的是输入,出站指的是输出。后面也会有详细介绍。Netty中的出/入站与Java NIO中的出/入站有些不同,Netty的出站可以理解为从Handler传递到Channel的操作,比如write写通道、read读通道数据;Netty的入站可以理解从Channel传递到Handler的操作,比如Channel数据过来之后,会触发Handler的channelRead()入站处理方法。

3 Netty中的Reactor模式

        先回顾一下Java NIO中IO事件的处理流程和Reac模式的基础内容

3.1 回顾Reactor模式中IO事件的处理流程

        一个IO事件从操作系统底层产生后,在Reactor模式中的处理流程如下图:

Netty核心原理与基础实战(一)备份_第1张图片

        Reactor模式中IO事件的处理流程大致分为4步,具体如下:

        1、通道注册。IO事件源于通道(Channel),IO是和通道(对应于底层连接而言)强相关的。一个IO事件一定属于某个通道。如果要查询通道的事件,首先就要将通道注册到选择器。

        2、查询时间。在Reactor模式中,一个线程会负责一个反应器(或者SubReactor子反应器),不断的轮询,查询选择器中的IO事件(选择键)。

        3、事件分发。如果查询到IO事件,则分发给与IO事件有绑定关系的Handler业务处理器。

        4、完成真正的IO操作和业务处理,这一步由Handler业务处理器负责。

        以上四步就是整个Reactor模式的IO处理器流程。其中,前两步其实是Java NIO的功能,Reactor模式仅仅是利用了Java NIO的优势而已。

3.2 Netty中的Channel

        Channel组件是Netty中非常重要的组件,因为Reactor模式和通道紧密相关,反应器的查询和分发的IO时间爱你都来自Channel组件。

        Netty中不直接使用Java NIO的Channel组件,对Channel组件进行了自己的封装。Netty实现了一系列的Channel组件,为了支持多种通信协议,换句话说,对于每一种通信连接协议,Netty都实现了自己的通道。除了Java 的NIO,Netty还提供了Java面向流的OIO处理通道。

        总结起来,对应不同的协议,Netty实现了对应的通道,每一种协议基本上都有NIO和OIO两个版本。对于不同的协议,Netty中常见的通道类型如下:

        1、NioSocketChannel:异步非阻塞TCP Socket传输通道。

        2、NioServerSocketChannel:异步非阻塞TCP Socket服务端监听通道。

        3、NioDatagramChannel:异步非阻塞的UDP传输通道。

        4、NioSctpChannel:异步非阻塞Sctp传输通道。

        5、NioSctpServerChannel:异步非阻塞Sctp服务端监听通道。

        6、OioSocketChannel:同步阻塞TCP Socket传输通道。

        7、OioServerSocketChannel:同步阻塞TCP Socket服务端监听通道。

        8、OioDatagramChannel:同步阻塞UDP传输通道。

        9、OioSctpChannel:同步阻塞Sctp传输通道。

        10、OioSctpServerChannel:同步阻塞Sctp服务端监听通道。

        一般来说,服务端变成用到最多的通信协议还是TCP,对应的Netty传输通道类型为NioSocketChanle,Netty服务端监听通道类型为NioServerSocketChannel。不论是哪种通道类型,最主要的API和使用方式上和NioSocketChannel类基本都是相同的,更多的是底层的传输协议不同,而Netty做了屏蔽传输的差异。

        在Netty的NioSocketChannel内部封装了一个Java NIO的SelectableChannel成员,通过对该内部的Java NIO通道的封装,对Netty的NioSocketChannel通道上的所有IO操作最终都会落地到Java NIO的SelectableChannel底层通道。NioSocketChannel的继承关系如下图:

Netty核心原理与基础实战(一)备份_第2张图片

4 Netty中的Reactor

        在Reactor模式中,一个反应器(或者SubReactor子反应器)会由一个事件处理线程负责事件查询和分发。该线程不断进行轮询,通过Selector选择器不断查询注册过的IO事件(选择键)。如果查询到IO事件,就分发给Handler业务处理器。

        首先介绍一下Netty中的反应器组件。Netty中的反应器组件有多个实现类,这些实现类与其通道类型相匹配。对应于NioSocketChannel通道,Netty的反应器类为NioEventLoop(NIO事件轮询)。

        NioEventLoop类有两个重要的成员属性:Thread线程类的成员和Java NIO选择器的成员属性。NioEventLoop的继承关系和主要成员属性如下图:

 Netty核心原理与基础实战(一)备份_第3张图片

        通过这个关系图可以看出:NioEventLoop和前面讲的反应器实现在思路上是一致的:一个NioEventLoop拥有一个线程,负责一个Java NIO选择器的IO事件轮询。

        在Netty中,EventLoop反应器和Channel的关系是什么?理论上来说:一个EventLoop反应器和NettyChannel 通道是一对多的关系:一个反应器可以注册成千上万的通道,如下图

Netty核心原理与基础实战(一)备份_第4张图片

5 Netty中的Handler

        前面介绍过,Java NIO的IO事件类型包括下面四种:

        1、可读:SelectionKey.OP_READ。

        2、可写:SelectionKey.OP_WRITE。

        3、连接:SelectionKey.OP_CONNECT。

        4、接收: SelectionKey.OP_ACCEPT。

        在Netty中,EventLoop反应器内部有一个线程负责Java NIO选择器的事件的轮询,然后进行对应的事件分发。事件分发(Dispatch)的目标就是Netty的Handler。

        Netty的Handler分两大类:第一类是ChannelInboundHandler入站处理器;第二类是ChannelOutboundHandler出站处理器,二者都继承ChannelHandler处理器接口。继承关系如下:

Netty核心原理与基础实战(一)备份_第5张图片

        Netty入站处理的流程是什么?以底层的Java NIO中的OP_READ输入事件为例:在通道中发生了OP_READ事件后,会被EventLoop查询到,然后分发给ChannelInboundHandler入站处理器,调用对应的入站处理的read()方法。在ChannelInboundHandler入站处理器内部的read()方法具体实现中,可以从 通道读取数据。

        Netty中的入站处理触发的方向为从通道触发,ChannelInboundHandler入站处理器负责接收。Netty中的入站处理不仅仅是OP_READ输入事件的处理,还包括从通道底层触发,由Netty通过层层传递,调用ChannelInboundHandler入站处理器进行的其他某个处理。 

        Netty中的出站处理指的是从ChannelOutBoundHandler出站处理器到通道的某次IO操作。例如,在应用程序完成业务处理后,可以通过ChannelOutboundHandler出站处理器将处理的结果写入底层通道。最常用的一个方式就是write()方法,把数据写入通道。

        Netty中的出站处理不仅仅包括Java NIO的OP_WRITE可写事件,还包括Netty自身从处理器到通道方向的其他操作。OP_WRITE可写事件是 Java NIO的概念,和Netty的出站处理在概念上不是一个维度,Netty的出站处理是应用层维度的。

        无论是入站还是出站,Netty都提供了各自的默认适配器实现:ChannelInboundHandler的模式实现为ChannelInboundHandlerAdapter(入站处理适配器)。ChannelOutBoundHandler 的默认实现为ChannelOutBoundHandlerAdapter(出站处理适配器)。这两个默认的通道处理适配器分别实现了基本的入站操作和出站操作功能。如果要实现自己的业务处理器,不需要从零开始去实现处理器的接口,只需要继承通道处理适配器即可。

6 Netty中的Pipeline

        先梳理一下Netty的Reactor模式实现中各个组件之间的关系:

        1、反应器(包括SubReactor子反应器)和通道之间时一对多的关系:一个反应器可以查询多个通道的IO事件。

        2、通道和Handler处理器实例之间是多对多的关系:一个通道的IO事件可以被多个Handler实例处理;一个Handler处理器实例也能绑定到很多通道,处理多个通道的IO事件。

        那么,Netty是如何组织通道和Handler处理器实例之间的绑定关系呢?Netty设计了一个特殊的组件:ChannelPipeline(通道流水线)。它像一条通道,将绑定到一个通道的多个Handler处理器实例串联到一起,形成一条流水线。ChannelPipeline的默认实现实际上被设计成一个双向链表。所有Handler处理器实例被包装成双向链表的节点,被加入到ChannelPipeline中。

【说明】一个Netty的通道拥有一个ChannelPipeline类型的成员属性,属性名叫pipeline。

        以入站处理为例,每一个来自通道的IO事件都会进入一个ChannelPipeline。在进入第一个Handler处理器后,这个IO事件就按照既定的从前往后次序,在流水线上不断地向后流动,流向下一个Handler处理器。在向后流程的过程中,会出现3种情况:

        1、如果后面还有其他Handler入站处理器,那么IO事件可以交给下一个Handler处理器向后流动。

        2、如果后面没有其他的入站处理器,就意味着这个IO事件在此次流水线中的处理结束了。

        3、如果中间需要终止流动,可以选择不将IO事件交给下一个Handler处理器,流水线的执行也被终止了。

        Netty的通道流水线和普通的流水线不同,Netty的流水线是不是单向,是双向的,而普通的流水线基本上都是单向的。Netty是这样规定的:入站处理器的执行次序时从前到后,出站处理器的执行次序时从后到前。总之,IO事件在流水线上的执行次序与IO事件的类型有关,如下图:

Netty核心原理与基础实战(一)备份_第6张图片

        除了流动的方向与IO操作类型有关之外,流动过程中所经历的处理器类型也是与IO操作的类型有关。入站的IO操作只能从Inbound入站处理器类型的Handler流过;出站的IO操作也只能从Outbound出站处理器类型的Handler流过。 

你可能感兴趣的:(java,Netty,Java,NIO,Handler)