「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战」
前言
- 关于作者:励志不秃头的一个CURD的Java农民工,想挑战看看自己能完成多少天的更文挑战
- 关于文章:以下内容单纯为作者了解的,如有不对,欢迎各路大神指导,下面简单聊聊Netty服务端的流程
Netty服务端
Netty服务端启动时,需要完成以下这几件事:线程模型、I/O 模型、读写逻辑、绑定端口。
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try{
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyServerHandlerV1());
}
});
System.out.printf("netty server start");
ChannelFuture cf = bootstrap.bind(9000).sync();
cf.channel().closeFuture().sync();
}catch (InterruptedException e) {
e.printStackTrace();
}finally{
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
短短这几行代码,实际上Netty背后帮我们完成了很多的初始化,下面就让我们来看看
bossGroup 和 workGroup
我们在初始化下,会在bootstrap配置两个线程组:bossGroup 和 workerGroup;我们在一开始便new出来了,其中bossGroup 用来接入新的连接,然后将完成接入的新连接分配给 workerGroup,之后这个连接上的交互都由 workerGroup 来处理。
- bossGroup只接入连接
- workerGroup 实际干活 我们点进去 NioEventLoopGroup 类,可以发现实际执行初始化的NioEventLoopGroup重写的newChild方法
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
return new NioEventLoop(this, executor, (SelectorProvider)args[0], ((SelectStrategyFactory)args[1]).newSelectStrategy(), (RejectedExecutionHandler)args[2]);
}
紧接着进入在NioEventLoop类就可以看到实例化的代码了,我加了一点注释,方便理解
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider, SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
//初始化线程池和队列
super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
if (selectorProvider == null) {
throw new NullPointerException("selectorProvider");
} else if (strategy == null) {
throw new NullPointerException("selectStrategy");
} else {
this.provider = selectorProvider;
NioEventLoop.SelectorTuple selectorTuple = this.openSelector();
this.selector = selectorTuple.selector;
this.unwrappedSelector = selectorTuple.unwrappedSelector;
this.selectStrategy = strategy;
}
}
因此,可以得到以下流程图
设置I/O 模型
.channel(NioServerSocketChannel.class) 这是netty灵活的一种表现,只需要在 ServerBootstrap 修改下配置即可,比如上面配置的 channel 就是
NioServerSocketChannel.class,就是非阻塞 I/O,如果想配置 BIO,则改为OioServerSocketChannel.class即可
绑定端口监听
在完成端口监听时,每一次有新的channel进来时,都会完成一系列的操作,主要是完成我们自定义加入ChannelPipeline的业务逻辑
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyServerHandlerV1());
}
})
这就是整个netty服务端启动的流程图,看起来很复杂的样子,但是跟着流程图一步步向下走还是很好明白的,主要要有多一点耐心。
- bossGroup 会处理新连接的请求,为新连接生成一个子 Channel,将其注册到 workerGroup 中的其中一个 EventLoop
- EventLoop在整个生命周期会不断地循环此 Channel 是否有事件发生,有就处理
- 业务逻辑,通过在pipeline 加入的ChannelHandler顺序处理,直接遍历 ChannelPipeline ,逐个调用对应的 ChannelHandler 来进行事件的处理
- ChannelPipeline 是一个双向链表,主要是为了收和发消息;链表链的是ChannelHandlerContext,ChannelHandlerContext保存了ChannelHandler的上下文
总结
bossGroup 和 workerGroup 两个线程组,分别处理连接建立和发生I/O 事件时的处理,channel都交给了workerGroup中的其中一个eventLoop,由这个eventLoop处理改channel的所有发生事件。
具体逻辑都在ChannelHandler中,分成了入站 ChannelHandler 和出站 ChannelHandler 两个类型,它们会串成链表形成一个 ChannelPipeline 。每个channel单独有一个ChannelPipeline,当有I/O事件发生中,就会遍历ChannelPipeline,顺着里面的ChannelHandler 一步步的执行业务逻辑处理。
本篇文章站在高处大概过了一遍netty的服务端流程和关键代码,其实netty的流程不算太复杂,只要有点耐心总是可以搞懂得(虽然我也绕晕了几遍),一些设计和思想挺值得学习的,对于客户端的流程,会比服务端的简单,我会在下篇文章把整个流程图放出来,我是新生代农民工L_Denny,我们下篇文章再见。