Netty源码分析1:启动过程

1.新建bootstrap(用户调用层)

//serverbootstrap初始化什么也不做
public ServerBootstrap() {
    }

2.为Bootstrap新增组件(用户调用层)

b.group(bossGroup, workerGroup)
       .channel(NioServerSocketChannel.class)
       .childOption(ChannelOption.TCP_NODELAY, true)
       .childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue")
       .handler(new ServerHandler())
       .childHandler(new ChannelInitializer<SocketChannel>() {
             @Override
             public void initChannel(SocketChannel ch) {
                 ch.pipeline().addLast(new AuthHandler());  
             }
        });

3.正式启动(进入源码):

ChannelFuture f = b.bind(8888).sync();
  • 四大步骤启动:
    • 创建服务端channel
    • 初始化服务端channel
    • 注册selector
    • 端口绑定

对应代码调用:

  • AbstractBootstrap bind()
    • initAndRegister()->channelFactory.newChannel();//制造一个channel
    • initAndRegister()->init(channel);//初始化channel
    • initAndRegister()->register//注册selector
    • dobind()//绑定端口

3.1创建服务端channel

通过绑定.class,工厂反射创建channel;

AbstractBootstrap.java

    public ChannelFuture bind(int inetPort) {
        return this.bind(new InetSocketAddress(inetPort));//step:1
    }
...
    public ChannelFuture bind(SocketAddress localAddress) {
        this.validate();
        if (localAddress == null) {
            throw new NullPointerException("localAddress");
        } else {
            return this.doBind(localAddress);//step:2
        }
    }
...
    private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = this.initAndRegister();//step:3    //...
    }
...
    final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = this.channelFactory.newChannel();//3.1内容制造一个channel
            this.init(channel);// 3.2内容:初始化
        } 
    //...
    }

可以看到通过channelFactoryinitAndRegister()制造了一个channel,channelFactory的产生代码

ReflectiveChannelFactory.java

    private final Class<? extends T> clazz;
...
    public T newChannel() {
        try {
            return (Channel)this.clazz.newInstance();//反射机制
        }
        //...
    }

这里显然是在之前已经绑定好了工厂生产的产品,是在用户自己初始化的时候绑定的

b.group(bossGroup, workerGroup)
       .channel(NioServerSocketChannel.class)
       .....

是在AbstractBootstrap.java 中这个函数创建了上面的工厂,并对channel类型绑定

    public B channel(Class<? extends C> channelClass) {
        if (channelClass == null) {
            throw new NullPointerException("channelClass");
        } else {
            return this.channelFactory((io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory(channelClass)));
        }
    }
3.1.2 NioServerSocketChannel实例化的过程

1.反射的东东看完了,来看下channel是怎么new 出来的:

NioServerSocketChannel.java

    public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }
//...
    private static java.nio.channels.ServerSocketChannel newSocket(SelectorProvider provider) {
        try {
            return provider.openServerSocketChannel();
        } 
        //...
    }

而其中的这个provider是

NioServerSocketChannel.java

import java.nio.channels.spi.SelectorProvider;
class NioServerSocketChannel{
    //...
    private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER =                                                                            SelectorProvider.provider();
    //...
}

2.使channel非阻塞

NioServerSocketChannel.java

    public NioServerSocketChannel(java.nio.channels.ServerSocketChannel channel) {
        super((Channel)null, channel, 16);// step1:调用父类AbstractNioMessageChannel
        //...
    }

上述代码调用AbstractNioMessageChannel的构造函数,而后者也是直接调用AbstractNioChannel的构造函数,对于16这个参数:应该是TCP的Accept属性,将这个服务端socket设为接收请求的socket;

AbstractNioChannel.java

    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        //...
        try {
            ch.configureBlocking(false);//step2:这一部将jdk底层调用产生的channel变成非阻塞
        }
        //...
    }

3.创建channel有关的的id,unsafe,pipline等

在2的最后一步try非阻塞前,AbstrctNioChannel调用其父类AbstrctChannel

AbstrctChannel.java

    protected AbstractChannel(Channel parent) {
        this.parent = parent;
        this.id = this.newId();  //channel唯一id
        this.unsafe = this.newUnsafe();//tcp读写底层类
        this.pipeline = this.newChannelPipeline();//pipline创建
    }

4.然后回到NioServerSocketChannel,TCP参数配置存放在这个NioServerSocketChannelConfig里面

    public NioServerSocketChannel(java.nio.channels.ServerSocketChannel channel) {
        //...
        this.config = new NioServerSocketChannel.NioServerSocketChannelConfig(
            this, this.javaChannel().socket()
        );
    }

3.2初始化服务端channel

在init函数里面有四个步骤:

3.2.1 配置自身channel的option
    void init(Channel channel) throws Exception {
        Map<ChannelOption<?>, Object> options = this.options0();
        synchronized(options) {
            channel.config().setOptions(options);// 1
        }
        Map<AttributeKey<?>, Object> attrs = this.attrs0();//2
        //...
    }    

这一块的option默认是空的,而设置的地方就是在下图中最开始bootstrap的那一堆链式调用初始化里面自己指定,然后在上面代码中options()调用获取

image.png

同理,也会顺着将创建channel前就设置的attribute正式绑定到channel(如上面代码 //2)

3.2.2配置客户端产生连接后的childChannel的option

这一步类似上一步,获取到childChannel的option和attribute,但这里是暂存,暂存原因在3.2.4解释:

    void init(Channel channel) throws Exception {
        //...3.2.1部分...
        synchronized(this.childOptions) {
                currentChildOptions = (Entry[])this.childOptions
                                    .entrySet()
                                    .toArray(newOptionArray(this.childOptions.size()));
                }

        final Entry[] currentChildAttrs;
        synchronized(this.childAttrs) {
                currentChildAttrs = (Entry[])this
                                    .childAttrs
                                    .entrySet()
                                    .toArray(newAttrArray(this.childAttrs.size()));
            }
3.2.3配置服务端的pipline

3.1中看到服务端的serversocketchannel绑定了一个Pipline,这里主要在pipline里面添加了一个channelhandler

这个handler也是用户在bootstrap一堆链式调用里面设置的,没有就算了

    void init(Channel channel) throws Exception {
        //...3.2.1    3.2.2部分...
        ChannelPipeline p = channel.pipeline();
        p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {
            public void initChannel(Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = ServerBootstrap.this.config.handler();
                if (handler != null) {
                    pipeline.addLast(new ChannelHandler[]{handler});
                }
                //...3.2.4
            }
        
3.2.4添加ServerBootstrapAcceptor用于把用户连接分配给线程

这里是个核心逻辑,本来就是一个普通的Nio封装,但是这一步将Acceptor作为了handller放在了最前面;

p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {
            public void initChannel(Channel ch) throws Exception {
                //... 3.2.3 ...内容
                ch.eventLoop().execute(new Runnable() {
                    public void run() {
                        pipeline.addLast(new ChannelHandler[]{
                            new ServerBootstrap
                                .ServerBootstrapAcceptor(currentChildGroup  // here
                                ,currentChildHandler //之前暂存的用户配置的handler放在这个后面
                                ,currentChildOptions, //之前暂存的用户配置的op放在这个后面 
                                currentChildAttrs)}); //之前暂存的用户配置的attr放在这个后面
                    }
                });
            }
        }});

至此,已经完成了启动过程的2/4步;

3.3 注册selector

同样在abstractBootStrap.java的initAndRegister(),完成上述两步之后,有一个函数

ChannelFuture regFuture = this.config().group().register(channel);

这里调用了AbstractChannel.register(channel),又分为几步

  • this.eventloop=eventloop 绑定线程
  • regitster0() 核心注册逻辑
    • doRegister()调用jdk把jdk原生channel注册到selector
    • 回调a:invokeHandlerAddedifNeed() 触发用户在Handler中编写的,channel注册的时候回调函数
    • 回调b:fireChannelRegister() 将注册成功的这个事件通知listener,触发用户在Handler中编写的的回调
    • 回调c:fireChannelActive();同上,都是用户写的回调触发
3.3.1绑定eventLoop

AbstractChannel.java

channel绑定eventLoop后,其后续的事件都会在eventLoop中

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    //....
    AbstractChannel.this.eventLoop = eventLoop;  //3.3.1
    if (eventLoop.inEventLoop()) {
                    this.register0(promise);     //3.3.2 a
                } else {
                    try {
                        eventLoop.execute(new Runnable() {
                            public void run() {
                                AbstractUnsafe.this.register0(promise);//3.3.2 b
                            }
                        });
                    }
    //....
3.3.2 注册selector

在《java读源码之netty》中,注册是执行的上述代码的3.3.2 a,然而自己调试的时候是在3.3.2 b处,看了下这个判断函数,是为了判断channel绑定的EventLoop启动线程与当前线程相同,相同表示已经启动,不同则有两种可能:未启动或者线程不同

public boolean inEventLoop(Thread thread) {
        return thread == this.thread;
}

因为这个监听的ServerSocketChannel是第一个channel,此时还没有已经开启运行的eventloop,这个时候就需要执行3.3.2b的启动了,eventLoop新开了一个线程,并且将channel在里面绑定;

但不管是3.2.2a 还是3.2.2b,最终都进入了——

private void register0(ChannelPromise promise) {
        //...
        AbstractChannel.this.doRegister();  //step 1
        //...
        AbstractChannel.this.pipeline.invokeHandlerAddedIfNeeded();//step 2a
                this.safeSetSuccess(promise);
                AbstractChannel.this.pipeline.fireChannelRegistered();//step 2b
                if (AbstractChannel.this.isActive()) {
                    if (firstRegistration) {
                        AbstractChannel.this.pipeline.fireChannelActive(); //step 2c
                    } else if (AbstractChannel.this.config().isAutoRead()) {
                        this.beginRead();
                    }
                }
  • step1 .doRegister()方法注册到selector

AbstractNioChannel.java

    protected void doRegister() throws Exception {
        boolean selected = false;

        while(true) {
            try {
                this.selectionKey = this.javaChannel()
                    .register(this.eventLoop().selector, 0, this);  //here
                return;
            } 

这里的this.javaChannel() this指的是netty封装后的channel,通过.javaChannel获取到其内部jdk底层的channel。

.register( xx,xx,xx) 第一个参数就是正常的jdk底层selector,第二个参数是一些设置(这里没设置),this是指netty自己的封装的channel,作为一个attachment放进去,这样当channel事件发生,就能通过selector直接获取到netty封装好的channel。

  • step2

    • 回调a 对应用户在Handlder中写的handler中的
    @Override
        public void handlerAdded(ChannelHandlerContext ctx) {
            System.out.println("handlerAdded");
        }
    
    • 回调b 对应用户在Handlder中写的handler中的
    @Override
        public void channelRegistered(ChannelHandlerContext ctx) {
            System.out.println("channelRegistered");
        }
    
    
    • 回调c 对应用户在Handlder中写的handler中的,但是这里并不是和step a b 紧接着调用,而是在active之后(第四步的端口绑定后)再次调用这个函数后才会active
    @Override
        public void channelActive(ChannelHandlerContext ctx) {
            System.out.println("channelActive");
        }
    

3.4 端口绑定

前面三步都是通过bind()调用doBind()再调用的initAndRegister()中实现,而端口绑定则是在doBind()中调用dobind0()实现,跳出了之前所关注的焦点initAndRegitser()

AbstractBootstrap.java中的dobind0()最终会调用AbstractChannel.javaBind()方法

    public final void bind(SocketAddress localAddress, ChannelPromise promise) {
            //...

                try {
                    AbstractChannel.this.doBind(localAddress);   //step 3.4.1
                } 
            //...
                if (!wasActive && AbstractChannel.this.isActive()) {
                    this.invokeLater(new Runnable() {
                        public void run() {
                            AbstractChannel.this.pipeline
                                .fireChannelActive();//step 3.4.2也就是在这调用用户的channelActive方法
                        }
                    });
                }

  • step 3.4.1 这一步最终调用的底层的channel的jdk原生bind绑定端口
  • step 3.4.2 fireChannelActive()触发pipline上的一系列active回调,最后将端口设置为允许接收请求
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容