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进行测试