netty核心组件开发

家住魔仙堡 提交于 2020-02-09 20:42:54
1)下载Netty包:
  Project Structure-->Modules-->Dependencies-->右边的+--Library-->From Maven-->io.netty:netty-all:4.1.20.Final(勾选Download Sources JavaDocs)

2)在Dependencies中勾选下载好的netty库

3)在工程的lib下发现了包,分为3类: 
  class(开发用的类)
  javadoc
  sources(源码文件): 里面还有个example目录,有很多例子

4)服务器启动流程:
  (1)初始化
      EventLoopGroup BossGroup(老板线程)
      EventLoopGroup WorkerGroup(工作线程)

  (2)初始化助手(链式操作):
    ServerBootStrap bootstrap
      1:group         设置2个线程组

      2:channel       设置NioSocketChannel作为通道的实现

      3:option        设置线程队列得到的连接个数

      4:childOption   设置保持活动连接状态

      5:childHandler  给我们的WorkerGroup的EventLoop对应的管道设置处理器:
          现有的
          自己自定义的
                        
    sync: 绑定一个端口并且同步,生成一个ChannelFuture对象,立即返回 // 启动服务器了,并且绑定了端口

    closeFuture().sync() 对关闭通道进行监听 // 异步模型

    shutdownGracefully: 优雅的关闭

    (3)服务器端Handler的编写:
      1.入栈的Handler的适配器: 我们自定义的Handler需要继承netty规定好的某个HandlerAdapter
      2.这是我们自定义一个Handler,才能成为一个Handler

      channelRead: 读取客户端发送的消息-->转发给Pipeline关联的Handler上:
        ChannelHandlerContext ctx: 上下文对象,含有管道pipeline(管道里面可以关联很多的Handler,像一个水流一样-->业务逻辑处理管道), 
            通道channel(数据的读写-->数据通道),地址
        Object msg: 客户端发送的数据,默认是Object-->要转成ByteBuf

      channelReadComplete: 读取数据完毕
        ctx.writeAndFlush(): 2个方法的合并,将数据写入到缓冲,写入到管道并刷新

      exceptionCaught: 发生异常,关闭通道


      --->Handler写好后,加入Pipeline

5)客户端启动流程
  (1)事件循环组:
    因为客户端发一个消息,服务器还有消息过来,肯定是要有一个循环组

  (2)启动助手 Bootstrap: 注意和服务器的不一样,少了个Server

    参数设置:
      group:    设置线程组
      channel:  设置客户端通道的实现类,要用反射来处理
      handler:  客户端也在不停的循环,也是需要一个Handler来处理;
              通过channel拿到pipeline-->向管道里面加入自己的处理器Handler

    connect: 连接 sync立马回返回

    关闭监听: ChannelFuture,涉及netty的异步模型

  (3)客户端Handler(注意事件驱动):
      1:继承: ChannelInboundHandlerAdapter-->Inbound就是入栈操作-->入栈、出栈就涉及到管道
      2:接口:
        channelActive: 当通道就绪就会触发
          writeAndFlush-->Unpooled.copiedBuffer拷贝 UTF8编码

        channelRead: 有数据可读被触发
          注意是ByteBuf

        exceptionCaught:


6)
  (1)默认8个EventLoop;
    Debug

  (2)NioEventLoopGroup
    EventExecutor
      8个NioEventLoop

  (3)但是BossGroup 1个也够了, 8个Worker

  (4)NettyServerHandler-->channelRead在不同的Worker线程中
     默认情况下,8个EventLoop,那么循环分配给客户端服务;
     每一个EventLoop有自己的Selector

  (5)channel与pipeline的关系(2个容易误会的概念):
      pipeline的本质是一个双向链表,出栈入栈的问题;

      Channel channel = ctx.channel();
      ChannelPipeline pipeline = ctx.pipeline();

    ctx(重量级对象):
      handler: 自己自定义的NettyServerHandler
      next:
      pre:
      inbound: true则表示入栈的状态
      pipeline: DefaultChannelPipeline
        head:头
        tail:尾
        channel:可以拿到管道,说明pipeline和channel是对应的关系,pipeline可以反向拿到channel,我中有你你中有我,相互包含; 
          localAddress:
          remoteAddress:
          eventLoop(反向拿到自己属于哪个EventLoop):

    总结:pipeline与channel是相互包含的关系,你中有我,我中有你;
        ctx更加强大,把pipeline和channel都包含了进去;

7)Netty模型中的任务队列
  (1)Selector

  (2)TaskQueue
    在事件循环中,在Pipeline中,我们会有一系列的任务Handler需要处理,a如果我们在Handler中我们如果有一个长 
      时间操作,那么势必会对Pipeline造成阻塞,这时我们可以把这些花费较长的业务提交到TaskQueue中进行异步处理,
      这样子Pipeline不会阻塞;
    TaskQueue和Channel是有一个绑定关系的;

  (3)3种任务队列
    1:用户自定义的普通任务:
      ctx.channel().eventLoop().execute(new Runnable(){
        public void run(){xxx}
      });

      说明: 把任务提交到Channel对应的NIOEventLoop的taskQueue中
        1:证明: ctx-->pipeline-->channel-->eventloop(NioEventLoop)-->taskQueue;
        2:taskQueue还是在一个线程,因此多个任务的话,后面的后执行,时间之和累计;

    2:用户自定义定时任务:
      通过schedule定时任务:

      说明: 把任务提交到了ScheduleTaskQueue
        证明: ctx-->pipeline-->channel-->eventloop(NioEventLoop)-->scheduleTaskQueue

    3:非当前Reactor线程调用Channel的各种方法:
      例如:在推送系统的业务线程里面,根据用户的标识,找到对应的Channel引用,然后调用Write类方法向该用户推送信息,
        就会进入到这种场景,最终的Write会提交到任务队列中后被异步消费;
        因为一个用户对应一个Channel,通过用户标识找到用户的Channel,系统中维护所有的Channel,调用Write;

      说明:
        例子:
          1:一个Server  3个管道: A、B、C
          2:关键的地方:拿到各自的Channel,就可以拿到EventLoop,就可以推送过去
          3:维护各个Channel引用的实现思路:
            在NettyServer的initChannel中,每一个用户来了,都可以拿到SocketChannel,根据用户标识记录下,
              可以使用一个集合管理 SocketChannel,再推送消息时,可以将业务加入到各个Channel对应的NioEvevntLoop
              的taskQueue中 或者 scheduleTaskQueue

8)Netty模型再说明:
  (1)Netty抽象出两组线程池,BossGroup专门负责接收客户端连接,WorkerGroup专门负责网络读写操作;
  (2)NioEventLoop标识一个不断循环执行处理任务的线程,每个NioEventLoop都有一个Selector,用于监听绑定在其上的
    socket网络通道;
  (3)NioEventLoop内部采用串行化设计,从消息的 读取-->解码-->处理-->编码-->发送,始终由IO线程NioEventLoop负责:
    1:NioEventLoopGroup下包含多个NioEventLoop;
    2:每个NioEventLoop中包含有一个Selector,一个taskQueue;
    3:每个NioEventLoop的Selector上可以注册监听多个NioChannel;
    4:每个NioChannel只会绑定在一个唯一的NioEventLoop上;
    5:每个NioChannel都绑定有一个自己的ChannelPipeline;

9)异步模型分析
  ajax

  出栈入栈

  (1)ChannelFuture:

10)Netty开发Http服务 
  Pipeline独立,Handler也是独立的

  (1)Codec[coder decoder]: netty提供的编解码器 HttpServerCodec
  (2)添加一个自定义的处理器Handler
  (3)设置返回值类型
     设置返回的数据的长度

11)Netty核心组件
  (1)引导类
    Bootstrap: 客户端引导类
    ServerBootstrap: 服务器引导类

    作用:一个Netty应用通常由一个Bootstrap开始,主要作用是配置整个Netty程序,串联各个组件,Netty中
      BootStrap是客户单程序的启动引导类, ServerBootStrap是服务器端的引导类;

  (2)常用方法

    -----服务器-----
    1: public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup)
      该方法用于服务端,用来设置2个EventLoop

    2: public BootStrap channel(Class<? extends C> channelClass)
      该方法用来设置1个服务端的通道实现

    3: public BootStrap option(ChannelOption<T> option, T value)
      用来给ServerChannel添加配置

    4: public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value)
      用来给收到的通道添加配置

    5: public ServerBootstrap childHandler(ChannelHandler childHandler)
      该方法用来设置业务处理类(自定义的Handler)

      注意:
        handler: 该handler对应的bossGroup
        childHandler: 对应WorkerGroup

    6: public ChannelFuture bind(int inerPort): 该方法用于服务器端,用来设置占用的端口号

    -----客户端-----
    1: public BootStrap group(EventLoopGroup group)
      该方法用于客户端,用来设置1个EventLoopGroup

    2: public ChannelFuture connect(String inetHost, int inerPort)
      该方法用于客户端,用来连接服务器

  (3)异步类: 把操作变成异步的操作
    Channel channel():  返回当前正在进行IO操作的通道
    ChannelFuture sync: 等待异步操作执行完毕

  (4)Channel:
    1:配置
      cf.channel().config().xxx

    2:Channel的类型
      NioSocketChannel(常用)
      NioServerSocketChannel(常用)

      NioDatagramChannel

      NioSctpChannel
      NioSctpServerChannel

   (5)Selector
     1:Netty基于Selector对象实现IO多路复用,通过Selector一个线程可以监听多个连接的Channel事件
     2:当向一个Selector中注册Channel后,Selector内部的机制就可以自动不断的查询(Select)这些注册的Channel
      是否有已经就绪的IO事件(可读、可写、网络连接完成等),这样程序就可以很家的使用一个线程高效的管理多个Channel

  (6)ChannelHandler及其实现类
    1:ChannelHandler是一个接口,处理IO事件或拦截IO操作,并将其转发到其ChannelPipeline(业务处理链)中的下一个处理程序;
    2:ChannelHandler本身并没有提供很多方法,因为和这个接口有许多的方法需要实现,方法使用期间,可以继承它的子类;
    3:ChannelHandler及其实现类:

    总结: 入栈和出栈对应读写 

    ChannelInboundHandler:用于处理入栈IO事件
    ChannelOutboundHandler:用于处理出栈IO操作

    适配器类:
      ChannelInboundHandlerAdapter:用于处理入栈IO事件
      ChannelOutboundHandlerAdapter:用于处理出栈IO操作
      ChannelDuplexHandler: 用于处理入栈和 出栈事件(不用,容易混淆)

    理解出栈和入栈
      出栈:
        ChannelPipeline提供了ChannelHadler链的容器,以客户端应用程序为例子,如果事件的运动方向是从客户端-->服务器,那么这些事件
          为出栈的,即客户端发送给服务端的数据会通过Pipeline中的一系列ChannelOutboundHandler,并被这些Handler处理

      入栈:
        相反

      后面从:编码解码器例子来理解。

    我们经常需要自定义一个Handler类去继承ChannelInboundHandlerAdapter,然后重写相应的方法实现业务逻辑:
      public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler{
        channelRegister:           channel注册发生
        channelUnregister
        channelActive:             --通道就绪事件
        channelInactive
        channelRead:               --通道读取数据事件
        chanelReadComplete:        --数据读取完毕事件
        userEventTriggered
        channelWritabilityChanged
        exceptionCaught:           --通道发生异常事件

        参数: ChannelHandlerContext ctx
      }

      Handler是一个非常复杂的体系,构成了一个非常核心的业务处理的知识点;

  (7)Pipeline和ChannelPipeline(重点: 是一个双向链表,每个元素都是ChannelHandlerContext)
    1:ChannelPipeline是一个Handler的集合,它负责处理和拦截inbound或者outbound的事件和操作,相当于一个贯穿Netty的链
      (也可以这样理解:ChannelPipeline是保存ChannelHandler的List,用于处理或拦截Channel的入栈事件和出栈操作);

    2:Channelpipeline实现一种高级形式的 拦截过滤器 模式,使用户可以完全控制事件的处理方式,
      以及Channel中各个的CahnnelHandler如何交互;

                                               Channel
                                           ChannelPipeline

         head头                                                                 tail尾                
      ChannelHandler          ChannelHandler        ChannelHandler          ChannelHandler
    ChannelHandlerContext  ChannelHandlerContext  ChannelHandlerContext  ChannelHandlerContext   

    理解:
        一个Channel包含了一个ChannelPipeline(反过来认为也可以),而ChannelPipeline中又维护了一个由ChannelHandlerContext组成的双向
          链表,并且每个ChannelHandlerContext(真实类型是DefaultChannelHandlerContext)中又关联着一个ChannelHandler;

        入栈和出栈事件在一个双向链表中,出栈事件会从链表head往后传递到最后一个入栈的Handler,
          出栈事件会从聊表tail往前传递到最前一个出栈的Handler,两种类型的Handler互不干扰

   3:理解Pipeline到底是什么东西?
    ChannelPipeline pipeline = ch.pipeline();

    head:
      next(DefaultChannelHandlerContext: 证明了双向链表里面放的是ChannelHandlerContext, 而不是直接放的Handler)
          handler=TestServerInitializer(这个本身也是一个Handler)
          next=
            handler(HttpServerCodec 这也是一个Handler)
            next=
              handler=DefaultChannelHandlerContext(TestHttpServerHandler, 这)
              next=DefaultChannelPipeline(这是最后一个了,而且没有包含Handler了)
                next=null
          prev=
      prev = null

    tail:

    channel:  说明Pipeline可以拿到channel

    入栈:(Server)head-->tail(Client), 因此服务器到客户端的话,是一个入栈,  客户端往服务器流动的话,认为是一个出栈
    出栈:   tail-->head

    ctx.getClass()-->得到真实类型 DefaultChannelPipeline

    4:常用的方法(把你的Handler放到哪里去)
      ChannelPipeline addFirst(ChannelHandler handlers): 把一个业务处理类(Handler)添加到链中的第一个位置;
      ChannelPipeline addLast(ChannelHandler handlers): 把一个业务处理类(Handler)添加到链中的最后一个位置;

  (8)ChannelHandlerContext
    1:保存Channel相关的上下文信息,同时关联一个ChannelHandler对象;
    2:即ChannelHandlerContext中包含一个具体的事件处理器ChannelHandler,
      同时ChannelHandlerContext中也绑定了对应的pipeline和Channel的信息,
      方便对ChannelHandler进行调用;
    3:常用方法:
      ChannelFuture close() 关闭通道

      ChannelOutboundinvoker flush() 刷新

      ChannelFuture writeAndFlush(Object msg): 将数据写到ChannelPipeline中当前ChannelHandler的下一个ChannelHandler(出栈)

  (9)ChannelOption
    Netty在创建Channel实例后,一般都需要设置ChannelOption参数:
      1:ChannelOption.SO_BACKING
        可连接队列大小

      2:ChannelOption.SO_KEEPALIVE 一直保持连接活动状态

  (10)EventLoopGroup和其实现类NioEventLoopGroup  
    1:EventLoopGroup是一组EventLoopGroup的抽象,Netty为了更好的利用多核CPU资源,一般会有多个EventLoop同时工作,
        每个EventLoop维护着一个Selector实例;

    2:EventLoopGroup提供next接口,可以从组里面按照一定规则获取其中一个EventLoop来处理任务,在NEtty服务器编程中,
      我们一般都需要提供2个EventLoopGroup,例如 BossEventLoopGroup和WorkerEventLoopGroup;

    3:通常一个服务端口即一个ServerSocketChannel对应一个Selector和一个EventLoop线程,BossEventLoop负责接收客户端的连接
      并将SocketChannel交给WorkerEventLoopGroup来进行IO处理,如下:
        1:BossEventLoopGroup通常是一个单线程的EventLoop,EventLoop维护者一个注册了ServerSocketChannel的Selector
          实例,BossEventLoop不断轮训Selector将连接事件分离出来;
        2:通常是OP_ACCEPT事件,然后将接收到的SocketChannel交给WorkerEventLoopGroup
        3:WorkerEventLoopGroup会由next选择其中一个EventLoopGroup来讲这个SocketChannel注册到其维护的Selector并
          讲其后续的IO事件进行处理

    常用方法:  
      shutdownGracefully: 断开连接 + 关闭线程 2个功能


12)Unpooled类
  (1)Netty提供一个专门用来操作缓冲区(即Netty的数据容器)的工具类

  (2)常用方法:
    public static ByteBuf copiedBuffer(CharSequence string, Charset charset)

  (3)readaerIndex

    writerIndex

    capacity

  (4)writeByte
    整型写入时可以被自动转化为byte

    netty的buffer中,不需要flip翻转,因为里面维护了readaerIndex writerIndex 2个属性
 
  (5)buffer:
    1:
      array:
      readerIndex: 下一个读取的位置
      writerIndex: 下一个写入的位置

    2: 0 <= readerIndex <= writerIndex <= capacity

    3: 方法:
      readByte(): 会引起readerIndex的增加
      getByte(int index): 获取固定位置,不造成增加
      readableBytes(): 可读字节数
      capacity(): 容量和里面实际存放数据多少是不一样的

    4: 读可区域: readerIndex-->writerIndex
       可写区域: writer-->capacity

    5: 发送过程: 先将字符串转化为ByteBuf,然后发出

13)群聊系统
  (1)channel: 服务器用的实现类
  (2)option: ChannelOption.SO_BACKLONG  128
  (3)childOption: 设置连接 ChannelOption.SO_KEEPALIVE, true
  (4)childHandler: 给Pipeline指定相关的Handler
          先获取到Pipeline

          StringDecoder 接收数据,要先解码,也是Handler
          StringDecoder 将来会送数据也要编码
          加入自己的业务处理Handler

  (5)集合的管理
    static Channel组:
      必须是static,因为每一个Channel都有一个Handler;
      private static ChannelGroup  channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE):
        GlobalEventExcuter.INSTANCE 是一个全局的事件执行器,是一个单例;

    static ArrayList


    重要接口:
      handlerAdded:   连接建立,一旦连接建立,第一个被执行--需要add;
      handlerRemoved: 客户端断开连接,将xx客户端离开信息推送给当前在线用户--ChannelGroup的大小自动减小;

      channelActive:   处理活动状态, 提示xxx上线,只在服务器打印下就行;
      channelInactive: 处于不活动状态,提示xxx离线;
      channelRead0:    读取数据

  (6)记录登录状态进行私聊
    Map<User, Channel>
    Map<String, Channel>

14)Netty心跳检测机制
  (1)IdleStateHandler
    表示多长时间没有读,就会发送一个心跳连接检测是否连接
    表示多长时间没有写,就会发送一个心跳连接检测是否连接
    表示多长时间没有读写,就会发送一个心跳连接检测是否连接

  (2)心跳检测不是那么的简单
    当能触发handlerRemoved是非常好,但是有时断掉了,服务器根本无法感知:
      如: 手机处于飞行状态, 强制关机
        所以只有通过心跳包才能真正的检测有效的连接;

  (3)处理空闲检测进一步处理的自定义的Handler,通过userEventTriggered:
    1:获取哪个通道(客户端)发生了超时事件
      ctx.channel().remoteAddress

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!