网络编程6

Netty组件再了解–Channel、EventLoop(Group) 和ChannelFuture

  • Channel—理解成原生编程中的Socket;
    • 抽象出来的通讯接口
    • 包含进行socket通讯时相关的各种方法
    • 除了对网络通道的相关抽象外,netty还提供了它认为需要的高级特性,在Channel接口里面还有一个内部接口Unsafe
  • ChannelFuture—异步通知。
  • EventLoop—控制、多线程处理、并发;
    • 理解成一个线程
    • 创建出来的channel,会和某个EventLoop进行一对一的挂钩,相当于把channel注册到EventLoop上
    • 在channel生命周期内发生所有的与网络相关的事情都由挂钩的EventLoop进行专门的处理

channel是有生命周期的

  • ChannelRegistered -> ChannelActive -> ChannelInactive -> ChannelUnregistered
    
  • channelRegistered------首先channel是未注册状态,所以调用注册方法,将channel注册到EventLoop之上

  • channelActive------因为channel是对socket的抽象,当和网络通讯的对端建立连接后,就处于激活状态

  • channelInactive-----当把channel关闭后,channel就不再连接到远程节点了

  • channelUnregistered-----在上一步关闭连接的同时,channel还需要和之前绑定的EventLoop进行一个注册关系的取消

  • channel会有几个生命周期状态的变化,就会产生一个相对应的事件,这些对应的事件就会在ChannelPipeline上面进行相关的流转,我们可以根据我们自己的需要,在这四个阶段里面写下相关的业务代码

  • 比如,之前实现的很简单的hello netty的netty小程序中,就是利用channelActive写下了发送hello netty的业务逻辑

channel.eventLoop

  • 返回它关联的eventLoop

EventLoop和EventLoopGroup

最开始写nio的程序怎么处理事件?

  • 在一个循环里面while(started){}
  • selector出相关的事件,依次通过handInput(key)方法去进行进一步的处理

EventLoop的作用

  • 专门用来处理网络连接生命周期发生的各种事件,但是不是只处理一次,而是反复的处理

EventLoop和EventLoopGroup关系说明

  • 一个EventLoopGroup 包含一个或者多个EventLoop;
  • 一个EventLoop 在它的生命周期内只和一个Thread 绑定;
  • 所有由EventLoop 处理的I/O 事件都将在它专有的Thread 上被处理;
  • 一个Channel 在它的生命周期内只注册于一个EventLoop;
    • 一个channel只对应一个eventloop
  • 一个EventLoop 可能会被分配给一个或多个Channel。
    • 一个eventloop可以对应对个channel,也就是之前说的reactor模式

netty为了避免多线程竞争,并行冲突,怎么解决的?

  • EventLoop在执行某个任务的时候,会做一个判定

  • 执行当前任务的线程,和eventloop绑定的线程是否是同一个

  • 如果不是同一个,就会把这个任务放到eventloop专属的一个队列中去,如果是同一个,就直接执行

  • 所以有线程,又有队列,这像什么?

  • jdk中有一个single的线程池,只有一个线程的线程池,所以eventloop本质上是一个单线程的线程池

ChannelHandler和它的适配器

  • 应用程序开发人员的角度来看,Netty 的主要组件是ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的地方。Netty 以适配器类的形式提供了大量默认的ChannelHandler 实现,帮我们简化应用程序处理逻辑的开发过程。

    • 比如说http、ssl、websocket,已经写好了各种handler
  • 在netty中也有专门一个接口ChannelHandler

    • ChannelInboundHandler,入站处理

      例如读取网络数据channelRead

    • ChannelOutboundHandler,出站处理

      bind、connect、write、read

      为什么read也是出站?业务程序告诉channel,现在请你读更多的信息给我,所以也算是一种出站

  • 如果自己要实现一个handler

    • ChannelHandlerAdapter

      ChannelInboundHandlerAdapter

      ChannelOutboundHandlerAdapter

    • 在实际中,这三个用的更多,只实现自己想实现的方法,不用全部实现

  • SimpleChannelInboundHandler

    • nio中的buffer,创建之后如果不释放,不清空,会造成内存的泄漏

    • 在ChannelOutboundHandler中,覆盖write操作,如果我们又没有往通道上写,需要我们自己释放buffer,调用一个ReferenceCountUtil.release()方法

    • i.在ChannelInboundHandlerAdapter之下,又扩展了一层

      例如在channelRead方法,如果不往后面继续传递,只是在当前handler中处理,处理完后,最终还要通过finally释放buffer,不然会造成内存泄漏

      ii.而之前写的EchoClientHandler中是实现了channelRead0方法

      这种handler的实现就是典型的责任链模式—要么自我释放、要么往后继续传递

ChannelPipeline和ChannelHandlerContext

  • ChannelPipeline 提供了ChannelHandler 链的容器,并定义了用于在该链上传播入站和出站事件流的API。

    • netty本身在pipeline里面实现了两个handler,一个叫head,一个叫tail

    • ChannelHandler里面既有add方法又有remove方法,所以ChannelHandler在处理的过程中可以随时加入到pipeLine,又可以从pipeLine中删除,是可以动态添加或删除的

    • 为什么ChannelPipeline提供动态添加或删除?

      根据业务,弹性调整需要的handler

    • bootStrap.handler(new ChannelInitializer(){}

      这里的ChannelInitializer也是继承了ChannelInboundHandlerAdapter

    • ChannelPipeline也是一个接口

      里面有各种add、fire、remove等各种方法

      fire----必须手动调用fire方法,才会让下一个handler继续处理,责任链模式,在servlet中也是这么处理的

    • ChannelPipeline是一个双向链表

    • ChannelPipeline中的handler运行时,入栈数据只会由入栈handler处理,出站数据只会由出站handler处理,pipeline在责任链中处理的时候会去判断下一个handler是否是入栈handler,如果不是就继续往下一个移动

    • netty对入站和出站各自的顺序要求是不能变的,即同一个方向的要求是不能变的

      假设都是入栈handler,第二个handler的处理依赖于第一个handler的处理结果,所以不能打乱顺序

  • 当ChannelHandler 被添加到ChannelPipeline 时,它将会被分配一个ChannelHandlerContext,其代表了ChannelHandler 和ChannelPipeline 之间的绑定。

    • ChannelHandlerContext用来维护handler的上下节点关系

    • 可以理解为链表漏斗

    • 和channel、channelPipeline中有一些方法是重复的

      channel ----- read方法

      channelPipeline ----- 各种fire方法

Channel ChannelPipeline和ChannelHandlerContext上的事件传播

  • ChannelHandlerContext.channel.write()和ChannelHandlerContext.pipeline.write()有什么区别呢?
    • 如果调用上述两者,则要求所有的出站handler全部都要走一遍
    • 如果在某一个context上调用ChannelHandlerContext.write(),则从则一个开始进行责任链的串行执行
  • 为什么要这么设计?
    • 还是以https为例,入栈1负责加密解密,入栈2负责解析信息,进行业务代码处理,如果入栈1解密失败,如果调用全调的那两种,则所有的出站handler全部都要走一遍,但是没有必要走那么多,只是简单的返回给通讯端提示解密失败即可

选择合适的内置通信传输模式

  • NIO
  • Epoll
    • 基于nio,只在linux上实现了,使用jni进行linux的驱动
  • OIO
    • 也就是常说的bio
  • Local
    • 在jvm通过管道通讯,涉及到进程之间的管道通讯、本地传输的问题
  • Embedded
    • 主要用在单元测试上面
    • 每写一个handler,来对这个单独的handler进行测试

猜你喜欢

转载自blog.csdn.net/Markland_l/article/details/113821546