Netty一些基本组件
Channel
介绍
这里先理解网络IO相关的Channel,理解Netty网络相关的Channel,可以先了解一下Java NIO的Channel,下面是NIO常用的两种用于处理网络IO的Channel
- ServerSocketChannel:用于监听新连接请求,只存在于服务端,在启动端口时创建,对应一个端口
- SocketChannel:用于服务端与客户端交互,同时存在服务端与客户端,在建立连接时创建,对应一个连接
Netty基于以上两个Channel实现了两个对应的Channel,NioServerSocketChannel和NioSocketChannel,这两个channel也是在使用Netty过程中最常用的,它们底层分别基于上面两个NIO的Channel,只是另外封装了一些Netty自己的东西,现在一般都是基于Bootstrap构建服务端或者客户端,所以一般都不需要自己定义Channel了,首先需要明确下面介绍一下在代码中channel的一些用法
NioServerSocketChannel
与Java NIO的ServerSocketChannel对应,绑定端口时生效,对应一个端口
使用Bootstrap绑定端口的时候,会得到一个FutureChannel,可以通过它获取NioServerSocketChannel,如下
1 | ChannelFuture future = bootstrap.bind(8080).sync(); |
获取NioServerSocketChannel,就可以对它进行添加处理器等操作,如下
1 | //不能使用addLast,因为NioServerSocketChannel默认的处理器没有执行ctx.fireChannelRead(msg),也就不会往下执行 |
NioSocketChannel
与Java NIO的SocketChannel对应,有新连接时创建,对应一个连接
获取NioSocketChannel的方法很多,比如直接使用上面的handler中的msg就是方法之一,一般常见的做法是通过bootstrap.childHandler设置NioSocketChannel的handler,可以在handler中获取,然后进行一些操作,如下
1 | bootstrap.childHandler( |
bootstrap.childHandler:该方法是为新的NioSocketChannel设置默认的处理器,并且可多次执行,如果仅需要对NioSocketChannel设置处理器,该方法就完全够用了
其他
除了Java的NIO(NIO底层基于select模型),Netty还实现了基于epoll的EpollServerSocketChannel和EpollSocketChannel(底层基于epoll模型,但仅适用于linux系统),还有一些其他网络IO相关channel,具体可查看代码
除了网络IO相关的Channel,还有文件IO相关的(比如实现了零拷贝),但是基本上大部分也是基于Java NIO
EventLoop
介绍
EventLoop是一个执行线程,负责处理Channel上的I/O事件和任务调度,确保所有I/O操作异步且非阻塞地执行,这里拿NioEventLoop举例
NioEventLoop有以下特点
- 内部包含一个selector
- 可以绑定一个或多个Channel(这一点基于selector,NioEventLoop与Channel绑定关系底层是selector与Java NIO的channel绑定(一对多)),Channel通常为NioServerSocketChannel和NioSocketChannel,需要注意,Channel只能绑定一个NioEventLoop
- 一个线程,它负责的IO事件和任务都在一个线程执行,它有一个名为run的核心方法,方法内部有一个for无限循环,所有工作都在for循环中同步执行
- 实现了Executor接口,包含了execute、submit、schedule等方法,可以用于执行特定的任务,有两个比较重要的队列,taskQueue和scheduledTaskQueue,用于存储普通任务和延时任务,实现了多生产者-单消费者模式,但是生产者、消费这和IO事件处理代码是同一线程,IO事件处理代码中添加的任务需要等到IO事件处理完成后才会执行
核心方法
这里重点讲解一下run方法,也是NioEventLoop的核心方法之一
1 | protected void run() { |
这里重点看一下调用的以下三个方法
- select:io.netty.channel.nio.NioEventLoop#select,作用是监听新的IO事件,会阻塞线程,底层调用的是selector.select,使用了一个参数curDeadlineNanos(该方法下一次返回的时间(有IO事件也会提前返回),为空则必须有IO事件才会返回),这个参数来源于NioEventLoop中的延时任务的下一次执行时间,目的是为了让延时任务正常执行
- processSelectedKeys:io.netty.channel.nio.NioEventLoop#processSelectedKeys,这个方法是处理监听到的IO事件(accept、read、write等),会循环处理,处理完一个再处理下一个,这里就有个问题,因为是单线程,如果processSelectedKeys方法执行过久,会导致很久都无法执行到runAllTasks方法,也就会导致定时任务不能按规定时间执行
- runAllTasks:io.netty.util.concurrent.SingleThreadEventExecutor#runAllTasks(),用于执行提交的任务,它会先调用fetchFromScheduledTaskQueue方法将scheduledTaskQueue中到期的任务放入taskQueue,再统一循环执行taskQueue中的所有任务,执行完runAllTasks方法后,并且channel没有关闭,则继续for循环,回到select方法继续阻塞,等待其下次返回
用法
在一般情况下可以在handler中通过ctx获取eventLoop,然后进行提交任务等操作,如下
1 | bootstrap.childHandler( |
上面的代码中提交了三个任务,这三个任务都不会立即执行,需要等到当前IO事件处理代码执行完之后才会在runAllTasks方法中执行,第三个任务也许更久,第一个和第二个的区别在于submit方法会返回Future
EventLoopGroup
EventLoopGroup 是 Netty 中用于管理多个 EventLoop 的组件,通常包含多个 EventLoop 以实现高效的并发处理
一般使用bootstrap.group方法设置EventLoopGroup,如下
1 | EventLoopGroup bossGroup = new NioEventLoopGroup(); |
这里的bossGroup负责处理ServerSocketChannel上的连接事件(这里bossGroup即使设置了多线程,但是一个ServerSocketChannel,即一个端口上也只会分配一个EventLoop),workerGroup则负责处理SocketChannel上的读写事件
EventLoopGroup的使用也决定了使用何种Reactor线程模型
单reactor、单线程模型如下
1 | EventLoopGroup group = new NioEventLoopGroup(1); |
上面的情况为ServerSocketChannel和SocketChannel共用一个EventLoopGroup,并且是单线程的
单reactor、多线程模型如下
1 | EventLoopGroup group = new NioEventLoopGroup(8); |
上面的情况为ServerSocketChannel和SocketChannel共用一个EventLoopGrop,但是是多线程的
多reactor、多线程模型如下
1 | EventLoopGroup bossGroup = new NioEventLoopGroup(8); |
上面的情况为ServerSocketChannel和SocketChannel使用不同的EventLoopGroup,并且是多线程的
ChannelHandler
ChannelHandler是Netty处理IO事件的核心组件,负责数据的编解码、业务逻辑处理等,比如上面介绍的
NioServerSocketChannel和NioSocketChannel添加的事件处理器,并且可以添加多个,ChannelHandler可以被多个Channel添加,和Channel是多对多的关系
可以理解为消息处理器,有两种:ChannelInboundHandler、ChannelOutboundHandler,前者是入站消息处理,当有读事件时,会生效,后者是出战消息处理,当由写消息时,会生效,此外还有一种特殊的双通道处理:ChannelDuplexHandler,读写事件它都会生效,因为他同时实现了上面两种Handler
ChannelPipeline
ChannelPipeline则是ChannelHandler的容器,它维护了一个ChannelHandler的有序列表,负责将事件传递给正确的ChannelHandler,并确保它们按照既定的顺序执行,它也是直接和channel和handler之间的桥梁
由多个ChannelHandler组成,当有IO读写事件时,如果是读事件,消息会先到达HeadContext(ChannelPipeline的头部),然后一直往下走,经过各种消息处理器,如果是写事件,当前消息处理器创建消息后,会往上走,到达HeadContext,然后发送出去,如果不是消息处理器创建的消息,而是channel直接创建的消息,就从管道尾部出发(TailContext),即入站消息从上往下,出战消息从下往上
ChannelHandlerContext
ChannelHandlerContext是ChannelHandler的上下文环境,提供了在ChannelHandler的业务代码中访问Channel、EventLoop、ChannelPipeline等组件的功能,它本身并不算组件
Codec
编解码器,一种特殊的ChannelHandler,因为netty本身只支持ByteBuf,所以需要编解码器将ByteBuf转为我们需要的类型,或者将我们生成的类型数据转为ByteBuf交给netty处理,它有两个主要的方法
- decode:负责解码,将ByteBuf转为我们需要的类型
- encode:负责编码,将我们生成的类型数据转为ByteBuf
codec常见的类有io.netty.handler.codec.http.HttpServerCodec
除了codec同时负责编码和解码以外,也有只负责其中一种的handler
- decoder(解码器):具有decode方法,实现ChannelInboundHandler,比如io.netty.handler.codec.http.HttpRequestDecoder
- encoder(编码器):具有encode方法,实现ChannelOutboundHandler,比如io.netty.handler.codec.http.HttpRequestEncoder
图解
