工作原理示意图1-简单版
Netty 主要基于主从 Reactors 多线程模型(如图)做了一定的改进,其中主从 Reactor 多 线程模型有多个 Reactor
说明
工作原理示意图2-进阶版
1、Boos Groop和Worker Group内部都包含多个NioEventLoop(又叫循环事件)。
2、每个NIOEventLoop对应一个Selector,
工作原理示意图-详细版
说明:
1、netty抽象出两个线程池BossGroup专门负责接收客户端的连接,WorkerGroup专门负责网络的读写。
2、BossGroup和WorkerGroup类型都是NioEventLoopGroup(事件循环组)。
3、NioEventLoopGroup相当于一个事件循环组,这个组中有多个事件循环,每一事件循环是NioEventLoop.
4、NioEventLoop表示的是一个不断循环的执行处理任务的线程,每个NioEventLoop都有一个Selector,用于监听绑定其上的socket的网络通讯。
5、NioEventLoopGroup可以有多个线程,并且可以设定个数,多少个线程对应的就是一个NioEventLoop
6、每个BossNioEventLoop循环的步骤有3步
1)轮训accept事件
2)处理accept事件,与client建立连接,生成SocketChannel在分装成NioSocketChannel,将其注册到某一个worker的NioEventLoop上的Selector上
3)处理任务队列的任务,及runAllTasks
7、每个worker NioEventLoop循环执行的步骤
1、轮训read,write事件
2、处理i/o事件,既read,write事件,在对应的NIoSockertChannel处理
3、处理任务队列的任务,既runAllTasks
8、每个worker NioEventLoop,处理业务时,会使用Pipeline(管道),pipeline中包含了Channel,即通过pipeline可以获取到对应的通道,管道中维护了很多的处理器。
友情提示:从源码中可以知道从Channel中可以获取Pipeline或者从Pipeline中获取到Channel,Channel主要作用是进行读写,Pipeline主要的作用是进行数据的业务处理,pipeline本质上是一个双向链表,包含一个出栈,入栈的问题
上下文对象包含
channel
pipeline
Netty 核心模块组
友情提示:感觉先介绍核心组件,在学习代码比较容易理解,Netty的代码都是这些组件结合实现代码的编写。
1、EventLoopGroup 和其实现类 NioEventLoopGroup
1.1、EventLoop
2、Bootstrap、ServerBootstrap
3、Channel
4、ChannelOption
5、Selector
6、ChannelHandler 及其实现类
7、ChannelHandlerContext
8、Pipeline 和 ChannelPipeline
9、Future、ChannelFuture、Promise
10、Unpooled
一开始需要树立正确的观念
- 把 channel 理解为数据的通道,可以想象成走水路呢,空陆呢,还是路陆呢
- 把 msg 理解为流动的数据,最开始输入是 ByteBuf,但经过 pipeline 的加工,会变成其它类型对象,最后输出又变成 ByteBuf,可以想象成交通工具,飞机,轮船,火车
- 把 handler 理解为数据的处理工序,可以想象成,到达目的的每一小步,到每一个站点使用什么方式去一个站点,比如我想去北京,我做火车到南京,到了南京做轮船,去上海,到了上海我做飞机去北京。这每一个站点就是一个Handler.
- 工序有多道,合在一起就是 pipeline,可以想象成,这完整的到达目的的线程流程就是Pipeline。pipeline 负责发布事件(读、读取完成...)传播给每个 handler, handler 对自己感兴趣的事件进行处理(重写了相应事件处理方法)
- handler 分 Inbound 和 Outbound 两类,可以想象成去北京,然后从北京回来。
- 把 eventLoop 理解为处理数据的工人(真正干活的,可以想象成,就是自己)
- 工人可以管理多个 channel 的 io 操作,并且一旦工人负责了某个 channel,就要负责到底(绑定)
- 工人既可以执行 io 操作,也可以进行任务处理,每位工人有任务队列,队列里可以堆放多个 channel 的待处理任务,任务分为普通任务、定时任务
- 工人按照 pipeline 顺序,依次按照 handler 的规划(代码)处理数据,可以为每道工序指定不同的工人
EventLoopGroup 和其实现类 NioEventLoopGroup
友情提示:
NioEventLoopGroup可以执行 io事件,普通任务,定时任务
DefaultEventLoopGroup只能执行普通任务,定时任务
友情提示:
EventLoopGroup 内部包含多个EventLoop,每个EventLoop关联一个Selector,多个Channel注册到Selector,BoosGroup的Selector主要监听Channel的连接,WorkerGroup的Selector主要监听Channel的读写操作。当BoosGroup的Selector监听到Channel的连接后,就会将其SockertChannel分装成功NioSocketChannel,WorkerGroup会调用next接口,调用其中的一个EventLoop将其注册到该EventLoop的Selector进行监听。
事件循环组
EventLoopGroup 是一组 EventLoop,Channel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop,后续这个 Channel 上的 io 事件都由此 EventLoop 来处理(保证了 io 事件处理时的线程安全)
- 继承自 netty 自己的 EventExecutorGroup
- 实现了 Iterable 接口提供遍历 EventLoop 的能力
- 另有 next 方法获取集合中下一个 EventLoop
常用方法
代码:
package cn.itcast.netty.c3;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.NettyRuntime;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
@Slf4j
public class TestEventLoop {
public static void main(String[] args) {
// 1. 创建事件循环组
EventLoopGroup group = new NioEventLoopGroup(2); // io事件,普通任务,定时任务
// EventLoopGroup group = new DefaultEventLoopGroup(); // 普通任务,定时任务
// 2. 获取下一个事件循环对象
System.out.println(group.next());
System.out.println(group.next());
System.out.println(group.next());
System.out.println(group.next());
// 3. 执行普通任务
// execute也可以换成submit,group.next().submit
/*group.next().execute(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("ok");
});*/
// 4. 执行定时任务,以一定的频率执行
group.next().scheduleAtFixedRate(() -> {
log.debug("ok");
// 第一个参数初始的延迟时间,如果为0就是立刻执行
// 第二个参数,就是每隔多长时间执行。
}, 0, 1, TimeUnit.SECONDS);
log.debug("main");
}
}
演示 NioEventLoop 处理 io 事件
服务器端两个 nio worker 工人
package cn.itcast.netty.c3;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class EventLoopServer {
public static void main(String[] args) {
// 细分2:创建一个独立的 EventLoopGroup
EventLoopGroup group = new DefaultEventLoopGroup();
new ServerBootstrap()
// boss 和 worker
// 细分1:boss 只负责 ServerSocketChannel 上 accept 事件 worker 只负责 socketChannel 上的读写
.group(new NioEventLoopGroup(), new NioEventLoopGroup(2))
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast("handler1", new ChannelInboundHandlerAdapter() {
@Override // ByteBuf
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
log.debug(buf.toString(Charset.defaultCharset()));
ctx.fireChannelRead(msg); // 让消息传递给下一个handler
}
});
/*.addLast(group, "handler2", new ChannelInboundHandlerAdapter() {
@Override // ByteBuf
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
log.debug(buf.toString(Charset.defaultCharset()));
}
});*/
}
})
.bind(8080);
}
}
结论:
可以看到两个工人轮流处理 channel,但工人与 channel 之间进行了绑定
再增加两个非 nio 工人
package cn.itcast.netty.c3;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class EventLoopServer {
public static void main(String[] args) {
// 细分2:创建一个独立的 EventLoopGroup
EventLoopGroup group = new DefaultEventLoopGroup(2);
new ServerBootstrap()
// boss 和 worker
// 细分1:boss 只负责 ServerSocketChannel 上 accept 事件 worker 只负责 socketChannel 上的读写
.group(new NioEventLoopGroup(), new NioEventLoopGroup(2))
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast("handler1", new ChannelInboundHandlerAdapter() {
@Override // ByteBuf
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
log.debug(buf.toString(Charset.defaultCharset()));
ctx.fireChannelRead(msg); // 让消息传递给下一个handler
}
})
.addLast(group, "handler2", new ChannelInboundHandlerAdapter() {
@Override // ByteBuf
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
log.debug(buf.toString(Charset.defaultCharset()));
}
});
}
})
.bind(8080);
}
}
结论:可以看到,nio 工人和 非 nio 工人也分别绑定了 channel(LoggingHandler 由 nio 工人执行,而我们自己的 handler 由非 nio 工人执行)
handler 执行中如何换人?
友情提示;针对上面的添加的非Nio工人,是怎么实现handler换人的。
从源码: io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead()
源码:
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRead(m);
} else {
executor.execute(new Runnable() {
public void run() {
next.invokeChannelRead(m);
}
});
}
}
说明:
- 如果两个 handler 绑定的是同一个线程,那么就直接调用
- 否则,把要调用的代码封装为一个任务对象,由下一个 handler 的线程来调用
EventLoop
可以理解为线程池+Selector,可以关联多个Channel.
事件循环对象