PS: 记录自己学习的过程,方便日后查阅。2022-02-06

Netty 做了件什么事?

学习一个东西,看它的作用是什么,Netty 说到底它就是一个网络框架,这个框架封装了 JDK 原生 API 对 socket 的一些操作,尤其是 NIO,使得网络编程变的简单,而且用 Netty 可以写出性能超级好的代码。

Netty 的思想

Netty 的核心是轮询处理事件,将传统一个线程对一个 IO 事件的模式,替换成将事件注册到链表中,然后使用一个线程去轮询链表;

每一个事件都会有一个 channel,每一个 channel 都会注册到 pipeline,pipeline 是个链表, 挂载了很多处理事件的节点;
image.png

其中对事件进出的处理用到了 intercepting-filter 模式,Netty 注释中给的处理过程:
image.png

通过 demo 跟源码

服务端启动 demo:

  1. public static void main(String[] args) {
  2. ServerBootstrap serverBootstrap = new ServerBootstrap();
  3. NioEventLoopGroup boos = new NioEventLoopGroup();
  4. NioEventLoopGroup worker = new NioEventLoopGroup();
  5. serverBootstrap.group(boos, worker)
  6. .channel(NioServerSocketChannel.class)
  7. .childHandler(new ChannelInitializer<NioSocketChannel>() {
  8. @Override
  9. protected void initChannel(NioSocketChannel ch) {
  10. ch.pipeline().addLast(new StringDecoder());
  11. ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
  12. @Override
  13. protected void channelRead0(ChannelHandlerContext ctx, String msg) {
  14. System.out.println(msg);
  15. }
  16. });
  17. }
  18. }).bind(8000);
  19. }

客户端启动 demo:

  1. public static void main(String[] args) throws InterruptedException {
  2. Bootstrap bootstrap = new Bootstrap();
  3. NioEventLoopGroup group = new NioEventLoopGroup();
  4. bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<Channel>() {
  5. @Override
  6. protected void initChannel(Channel ch) {
  7. ch.pipeline().addLast(new StringEncoder());
  8. }
  9. });
  10. Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();
  11. while (true) {
  12. channel.writeAndFlush(new Date() + ": hello world!");
  13. Thread.sleep(2000);
  14. }
  15. }

服务端启动流程

先看服务端启动,有一个 bind 端口的操作,其它的一些操作可以暂时先不管,那些都是一些配置类:
image.png
绑定的时候有一个初始化并且注册的动作,初始化什么,又注册什么呢?继续看代码:
image.png
可以看到,它从 Channel 工厂中创建了一个 channel 出来,然后去初始化,继续看初始化操作:
image.png
这个 init 方法是在抽象类中,它有两个子类实现了它,一个是客户端启动类,一个是服务端启动类,这个就是所谓的模板方法了,也就是说 Netty 客户端和服务端启动的时候,初始化 channel 的具体实现是不一样的。我们这里先启动的是服务端代码,所以先看服务端启动类对 init 方法的实现:
image.png
还记得一开始我们直接看 bind 操作,忽略了其它一些陌生的类和操作,在这个 init 方法中,我们都找到了,大致就是加载一些 key-value 的配置,然后从 channel 中获取到 pipeline 对象,这个 pipeline 是个链表,链表中每个节点是一个 handler,用来处理 channel 中的数据。也就是说 init 方法是初始化了一些 channel 配置和数据处理方式。继续看代码:
image.png
上面说道,除了初始化,还有一个注册,继续看代码:
image.png
一直点进去,最后再 doRegister 方法中,会发现有多种注册方式,我们这里是 NIO 方式,另一个经常看到的是 Epoll,它里面有个关键参数,叫做文件描述符,先按下不表。继续看 NIO 的方式:
image.png
NIO 的注册方式,是将该 channel(每一个 channel 都有一个自己的 pipeline) 注册到指定的 selector 中:
image.png
继续回到绑定部分,当注册成功后,会去执行绑定逻辑,这里的绑定是将该 IP+端口绑定在 ChannelPipeline 类中,而它则是一个链表,会作为一个节点挂载到尾结点中:
image.png
�io.netty.channel.DefaultChannelPipeline#bind(java.net.SocketAddress, io.netty.channel.ChannelPromise)
image.png
服务端启动流程小结:

  1. 通过 io.netty.bootstrap.ServerBootstrap 类,将配置串联起来
  2. 初始化 channel 中初始化各种配置,并将 channel 注册到 selector 中
  3. 将 IO 事件绑定到 pipeline 中,当前服务端启动产生一个新的 socket 也是一个 IO 事件

Netty 重要的几个类

NioEventLoopGroup 是 NioEventLoop 的集合,NioEventLoop 其实是一个事件执行器,处理所有 Channel 中的 IO 事件。

Selector

NioEventLoop
从命名来看它,NioEventLoopGroup 是 NioEventLoop 的集合,一个 Channel 会分配一个 EventLoop,它会绑定一个线程来处理一个 Channel 的所有 IO 事件。

Channel
网络 socket 的一系列 IO 操作基于它来完成,例如读、写、连接、绑定等

ChannelHandler
�Channel 处理器,用来处理 Channel 中的数据

ChannelPipeline
�一个 ChannelHandler 会添加到 ChannelPipeline 中。ChannelPipeline 责任链模式,pipeline 中会有很多的 ChannelHandler 依次处理 Channel 中的数据。

ServerBootstrap
�辅助类,将 Netty 相关组件配置类,串联起来。