Netty由三层结构构成,1.网络通信层2事件调度层3.服务编排层。
在网络通信层有三个核心组件Bootstrap 、ServerBootstrap和Channel,Bootstrap负责客户端启动并去连接远程的Netty Server,ServerBootstrap是负责服务端的监听,监听指定的一个端口,Channel是负责网络通信的一个载体,事件调度器。
事件调度器有两个核心角色,EventLoopGroup和EventLoop,EventLoopGroup本质上是一个线程池,主要去负责接收IO请求并分配线程去执行处理请求,EventLoop相对于线程池里的一个具体线程。
在服务编排层有三个核心组件,ChannelHandler(消息处理器) 、 ChannelPipeline(ChannelHandler 对象链表)和ChannelHandlerContext。ChannelPipeline负责处理多个ChannelHandler,它会把多个ChannelHandler构成一个链去形成这样一个Pipeline,ChannelHandler主要是针对IO数据的一个处理器,数据接收后通过指定的一个lHandler进行处理,ChannelHandlerContext是用来保存ChannelHandler的上下文信息的。
网络通信最终都是通过字节流进行传输的。 ByteBuf 就是 Netty 提供的一个字节容器,其内部是一个字节数组。 当我们通过 Netty 传输数据的时候,就是通过 ByteBuf 进行的。
我们可以将 ByteBuf 看作是 Netty 对 Java NIO 提供的ByteBuffer 字节容器的封装和抽象。为什么不直接使用 Java NIO 提供的 ByteBuffer 呢?因为 ByteBuffer 这个类使用起来过于复杂和繁琐。
Bootstrap 是客户端的启动引导类/辅助类
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建客户端启动引导/辅助类:Bootstrap
Bootstrap b = new Bootstrap();
//指定线程模型
b.group(group).
......
// 尝试建立连接
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} finally {
// 优雅关闭相关线程组资源
group.shutdownGracefully();
}
ServerBootstrap 是服务端的启动引导类/辅助类
// 1.bossGroup 用于接收连接,workerGroup 用于具体的处理
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//2.创建服务端启动引导/辅助类:ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
//3.给引导类配置两大线程组,确定了线程模型
b.group(bossGroup, workerGroup).
......
// 6.绑定端口
ChannelFuture f = b.bind(port).sync();
// 等待连接关闭
f.channel().closeFuture().sync();
} finally {
//7.优雅关闭相关线程组资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
Channel 接口是 Netty 对网络操作抽象类。通过 Channel 我们可以进行 I/O 操作。
一旦客户端成功连接服务端,就会新建一个 Channel 同该用户端进行绑定。
// 通过 Bootstrap 的 connect 方法连接到服务端
public Channel doConnect(InetSocketAddress inetSocketAddress) {
CompletableFuture<Channel> completableFuture = new CompletableFuture<>();
bootstrap.connect(inetSocketAddress).addListener((ChannelFutureListener)future -> {
if (future.isSuccess()) {
completableFuture.complete(future.channel());
} else {
throw new IllegalStateException();
}
});
return completableFuture.get();
}
比较常用的Channel 接口实现类是 :
NioServerSocketChannel(服务端)
NioSocketChannel(客户端)
这两个Channel 可以和 BIO 编程模型中的ServerSocket 以及Socket 两个概念对应上。
Netty是基于Selector对象实现的I/O多路复用,通过Selector 一个线程可以监听多个连接的Channel事件。
当向一个Selector中注册Channel后,Selector可以自动不断地查询(Select)这些注册的Channel 是否有已就绪的I/O事件,这样程序就可以很简单地使用一个线程高效地管理多个Channel 。
EventLoop(事件循环)接口可以说是 Netty 中最核心的概念了。EventLoop 的主要作用实际就是责监听网络事件并调用事件处理器进行相关 I/O 操作(读写)
的处理。
那 Channel 和 EventLoop 直接有啥联系呢?Channel 为 Netty 网络操作(读写等操作)抽象类,EventLoop 负责处理注册到其上的Channel 的 I/O操作,两者配合进行 I/O 操作。
EventloopGroup 和 EventLoop 的关系:EventLoopGroup 包含多个 EventLoop(每一个 EventLoop 通常内部包含一个线程),它管理着所有的 EventLoop 的生命周期。并且,EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理,即 Thread 和 EventLoop 属于1 : 1 的关系,从而保证线程安全。
Netty 的设计原则之一是:同一个 Channel 的所有 I/O 操作都由同一个 EventLoop 来处理,这样可以保证同一个 Channel 的所有 I/O 操作都是由同一个线程按照顺序执行的,避免了多线程并发处理同一个 Channel 时需要进行线程同步的开销。如果你想要并行地执行多个 I/O 操作,你需要创建多个 Channel,每个 Channel 对应一个 I/O 操作。这样,这些 I/O 操作就可能会被不同的 EventLoop(也就是不同的线程)并行执行。
回顾我们在上面写的服务器端的代码:
// 1.bossGroup 用于接收连接,workerGroup 用于具体的处理