PS: 记录自己学习的过程,方便日后查阅。2022-02-06
Netty 做了件什么事?
学习一个东西,看它的作用是什么,Netty 说到底它就是一个网络框架,这个框架封装了 JDK 原生 API 对 socket 的一些操作,尤其是 NIO,使得网络编程变的简单,而且用 Netty 可以写出性能超级好的代码。
Netty 的思想
Netty 的核心是轮询处理事件,将传统一个线程对一个 IO 事件的模式,替换成将事件注册到链表中,然后使用一个线程去轮询链表;
每一个事件都会有一个 channel,每一个 channel 都会注册到 pipeline,pipeline 是个链表, 挂载了很多处理事件的节点;
其中对事件进出的处理用到了 intercepting-filter 模式,Netty 注释中给的处理过程:
通过 demo 跟源码
服务端启动 demo:
public static void main(String[] args) {
ServerBootstrap serverBootstrap = new ServerBootstrap();
NioEventLoopGroup boos = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
serverBootstrap.group(boos, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
System.out.println(msg);
}
});
}
}).bind(8000);
}
客户端启动 demo:
public static void main(String[] args) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(new StringEncoder());
}
});
Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();
while (true) {
channel.writeAndFlush(new Date() + ": hello world!");
Thread.sleep(2000);
}
}
服务端启动流程
先看服务端启动,有一个 bind 端口的操作,其它的一些操作可以暂时先不管,那些都是一些配置类:
绑定的时候有一个初始化并且注册的动作,初始化什么,又注册什么呢?继续看代码:
可以看到,它从 Channel 工厂中创建了一个 channel 出来,然后去初始化,继续看初始化操作:
这个 init 方法是在抽象类中,它有两个子类实现了它,一个是客户端启动类,一个是服务端启动类,这个就是所谓的模板方法了,也就是说 Netty 客户端和服务端启动的时候,初始化 channel 的具体实现是不一样的。我们这里先启动的是服务端代码,所以先看服务端启动类对 init 方法的实现:
还记得一开始我们直接看 bind 操作,忽略了其它一些陌生的类和操作,在这个 init 方法中,我们都找到了,大致就是加载一些 key-value 的配置,然后从 channel 中获取到 pipeline 对象,这个 pipeline 是个链表,链表中每个节点是一个 handler,用来处理 channel 中的数据。也就是说 init 方法是初始化了一些 channel 配置和数据处理方式。继续看代码:
上面说道,除了初始化,还有一个注册,继续看代码:
一直点进去,最后再 doRegister 方法中,会发现有多种注册方式,我们这里是 NIO 方式,另一个经常看到的是 Epoll,它里面有个关键参数,叫做文件描述符,先按下不表。继续看 NIO 的方式:
NIO 的注册方式,是将该 channel(每一个 channel 都有一个自己的 pipeline) 注册到指定的 selector 中:
继续回到绑定部分,当注册成功后,会去执行绑定逻辑,这里的绑定是将该 IP+端口绑定在 ChannelPipeline 类中,而它则是一个链表,会作为一个节点挂载到尾结点中:
�io.netty.channel.DefaultChannelPipeline#bind(java.net.SocketAddress, io.netty.channel.ChannelPromise)
服务端启动流程小结:
- 通过 io.netty.bootstrap.ServerBootstrap 类,将配置串联起来
- 初始化 channel 中初始化各种配置,并将 channel 注册到 selector 中
- 将 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 相关组件配置类,串联起来。