likes
comments
collection
share

Netty 源码共读(一) 服务端启动流程

作者站长头像
站长
· 阅读数 16

前言

作为一个基本没有怎么系统性探索过源码的人来说,借此活动机会,也来简单学一下源码的阅读。

文明和谐,不喜勿喷

文章导读

📖 阅读本文,你将跟随我一起了解到

  • 克隆并运行测试张师傅给出的demo
  • 通过示例代码探索 Netty 服务端的启动流程

运行示例demo

学习源码,必不可少的我们要从一个简单的demo来入手。这里我们直接使用领读员张师傅在活动的给出的代码

克隆项目并运行

克隆项目

git clone https://github.com/arthur-zhang/netty-study.git

或者直接在IDEA中新建

Netty 源码共读(一) 服务端启动流程

运行项目

找到Myserver,并运行该项目。

Netty 源码共读(一) 服务端启动流程

这个服务只是个服务端。我们使用张师傅推荐的工具来充当客户端进行测试,这里选择了nc工具

Linux 下可以直接使用netcat命令。Windows下没有这个命令,需要自己安装。nc下载地址

解压文件夹,并将解压后的文件夹地址,加入到系统的环境变量即可。

Netty 源码共读(一) 服务端启动流程

至此,我们已经成功的运行该示例项目,并简单测试了代码的作用

一个简单的Echo 服务。返回向服务端发送的消息。

下面,就让我们通过该服务端示例代码,来探索下Netty服务端的启动流程

服务端启动流程探索

代码

public class MyServer {
    public static void main(String[] args) throws InterruptedException {
        //引导类 ①
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        //IO模型 ②
        serverBootstrap.channel(NioServerSocketChannel.class); 
        //临时存在已完成三次握手请求队列的的最大长度 ③
        serverBootstrap.option(NioChannelOption.SO_BACKLOG, 511);
        //开启 Nagle 算法
        serverBootstrap.childOption(NioChannelOption.TCP_NODELAY, true);
        //设置Log级别
        serverBootstrap.handler(new LoggingHandler(LogLevel.INFO));
        //监听端口,处理接收新的连接的 线程组 ④
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(0, new DefaultThreadFactory("boss"));
        //处理每一条连接的相关IO的线程组
        NioEventLoopGroup workGroup = new NioEventLoopGroup(0, new DefaultThreadFactory("worker"));
   
        try {
            // 配置两个主要的线程组 ⑤
            serverBootstrap.group(bossGroup, workGroup);
            //Netty对 NIO 类型连接的的抽象和处理。
            final MyEchoServerHandler serverHandler = new MyEchoServerHandler();
            serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) {
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast(new ServerIdleCheckHandler()); //⑥
                    pipeline.addLast(new LoggingHandler(LogLevel.INFO));
                    pipeline.addLast(serverHandler);
                }
            });
            //绑定启动端口 ⑦
            ChannelFuture f = serverBootstrap.bind(8888).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}

下面我们就通过这个代码,一步步的分析和了解他的过程。

ServerBootstrap

在代码的一开始,我们先定义了一个ServerBootstrap类。这个类的主要作用是来 完成服务器端的 Netty 初始化

.channel 设置netty的ServerSocketChannel模型

中,我们设置了

 serverBootstrap.channel(NioServerSocketChannel.class); 

从源码io.netty.bootstrap.AbstractBootstrap#channel看该方法的调用 Netty 源码共读(一) 服务端启动流程channel方法,创建了并返回了一个channelFactory的工厂实例ReflectiveChannelFactory。咋启动的时候通过这个去创建相应的channel实例。

常用的类型
  • NioSocketChannel/OioSocketChannel : 用于客户端的异步非阻塞/同步阻塞类型
  • NioServerSocketChannel/OioServerSocketChannel : 用于服务端的异步非阻塞/同步阻塞模型

.option 设置服务端接受TCP连接的相关设置

的示例代码中NioChannelOption.SO_BACKLOG,这个参数设置了服务端接受连接的队列的最大值, 它其实对应的就是是tcp/ip协议listen函数中的backlog参数,函数listen(int socketfd,int backlog)用来初始化服务端可连接队列。

因为服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小。当超过这个限制的时候,它就是拒绝客户端的连接。

当然,他不止这一个参数,还有SO_REUSEADDR,SO_KEEPALIVE等等。

.childOption 设置处理连接的线程的相关设置

相比较简单来说,.option是给bossGroup进行设置,那.childOption 就是给workGroup 就如例子中设置的TCP_NODELAY,表示是否立即是否立即返回数据。它同样也拥有多种的设置,如SO_RCVBUF设置缓冲区大小之类的。

两个NioEventLoopGroup线程组

  • bossGroup :处理客户端连接的线程组
  • workGroup :处理每一条连接相关IO事件的线程组

既然是线程组,那么它们默认包含了多少个线程呢。

示例代码④中,我们设置了0,从源码看

Netty 源码共读(一) 服务端启动流程

然后看它的this调用

Netty 源码共读(一) 服务端启动流程

继续看它的super

Netty 源码共读(一) 服务端启动流程

可以看到 当指定的nThreads设置为 0 时,它会有一个默认值 DEFAULT_EVENT_LOOP_THREADS,那么这个值是多少呢。

Netty 源码共读(一) 服务端启动流程

通过源码可以看到,默认的线程数是cpu核数的两倍。这个我们也可以通过查看我们的系统内核和代码来验证下

我的电脑cpu是12核的 Netty 源码共读(一) 服务端启动流程

在代码中打个断点,我们可以看到 每个线程组里面默认初始化了24个线程。 Netty 源码共读(一) 服务端启动流程

.group

Netty 源码共读(一) 服务端启动流程

从源码看,启动类通过.group,将bossGroupworkGroup设置给了ServerBootstrapparentGroupchildGroup。 我们上面的.option.childoption就是分别对两个线程组对应进行设置的。

设置流水线处理器

在客户端连接之后,我们那要对其连接进行处理。那要怎么处理,由谁来处理呢。

Netty 源码共读(一) 服务端启动流程 从源码来看,传入的childHandler赋值给ServerBootstrap的childHandler属性。我们通过设置channelHandler来处理客户端的请求的channelIO处理。

中,示例代码通过匿名内部类的方式实例化了一个ChannelInitializer,并重写了其initChannelf方法,在其中给流水线设置了我们自定义实现的处理器ServerIdleCheckHandlerMyEchoServerHandler。来实现我们消息返回处理。

.bind(8888).sync() 绑定端口,启动服务

这里是真正的服务启动的入口,提供用于服务端或者客户端绑定服务器地址和端口号,默认是异步启动。如果加上sync()方法则是同步。

我们从源码一步步看他是如何启动的。

Netty 源码共读(一) 服务端启动流程

Netty 源码共读(一) 服务端启动流程

首先是调用bind()。首先validate()对相关参数进行了校验。 Netty 源码共读(一) 服务端启动流程

然后调用到dobind()

首先是在initAndRegister()中,实例化了一个channel,是NioServerSocketChannel.class的一个实例,然后对其进行init(),设置我们在之前代码设置是属性。之后并将其进行注册register()

Netty 源码共读(一) 服务端启动流程 最后是调用doBind0() Netty 源码共读(一) 服务端启动流程

然后一步步追踪下去。最后在ServerSocketChannelImpl中完成了地址和端口的绑定。

Netty 源码共读(一) 服务端启动流程

总结

以上就是通过活动的示例代码。来简单探索了一下服务端启动流程。并通过示例代码中的代码,简单了解了下所涉及的对象和一些常用的方法和熟悉。当然,这只是一个简单了解。只是自我探索的一个记录。非喜勿喷~~

最后

我是Ylimhs,一个长期活跃于沸点的最佳摸鱼手,热爱Coding,喜欢分享,五湖四海皆兄弟,欢迎大家一起在沸点摸鱼ヾ(≧▽≦*)o