• Netty源码分析第1章(Netty启动流程)---->第5节: 绑定端口


     

    Netty源码分析第一章:Netty启动步骤

     

    第五节:绑定端口

    上一小节我们学习了channel注册在selector的步骤, 仅仅做了注册但并没有监听事件, 事件是如何监听的呢?

    我们继续跟第一小节的最初的doBind()方法:

    private ChannelFuture doBind(final SocketAddress localAddress) {
        //初始化并注册(1)
        final ChannelFuture regFuture = initAndRegister();
        //获得channel(2)
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }
        if (regFuture.isDone()) {
            ChannelPromise promise = channel.newPromise();
            //绑定(3) 
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
            //去除非关键代码
            return promise;
        }
    }

    上一小节跟完了initAndRegister()方法, 我们继续往下走:

     第二步, 获得channel:

    final Channel channel = regFuture.channel();

    通过ChannelFuture的channel()方法获得了我们刚刚注册的NioServerSocketChannel, 拿到这个channel我们跟到第三步, 绑定

    跟进方法doBind0(regFuture, channel, localAddress, promise):

    private static void doBind0(final ChannelFuture regFuture, final Channel channel, final SocketAddress localAddress, final ChannelPromise promise) { 
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                if (regFuture.isSuccess()) {
                    //绑定端口
                    channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                } else {
                    promise.setFailure(regFuture.cause());
                }
            }
        });
    }

    最终会走到channel.bind(localAddress, promise)这个方法当中

    继续跟, 会走到AbstractChannel的bind()方法中:

    public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
        //通过pipeline绑定端口
        return pipeline.bind(localAddress, promise);
    }

    这里的pipeline就是channel初始化创建的pipline, pipline是事件传输通道, 这里我们暂不跟传输过程, 我们只需知道最后这个方法走到了AbstractChannel的bind()方法

    跟到AbstractChannel的bind()方法:

    public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
        //代码省略
        //端口绑定之前不是active, 返回false
        boolean wasActive = isActive();
        try {
            //做jdk底层的绑定
            doBind(localAddress);
        } catch (Throwable t) {
            //省略
            return;
        }
        //端口绑定之前不是active, 端口绑定之后变成active了
        if (!wasActive && isActive()) {
            invokeLater(new Runnable() {
                @Override
                public void run() {
                    pipeline.fireChannelActive();
                }
            });
        }
        safeSetSuccess(promise);
    }

    重点关注下doBind(localAddress)方法

    跟到NioSeverSocketChannel的doBind()方法:

    protected void doBind(SocketAddress localAddress) throws Exception {
        //jdk版本的判断
        if (PlatformDependent.javaVersion() >= 7) {
            javaChannel().bind(localAddress, config.getBacklog());
        } else {
            javaChannel().socket().bind(localAddress, config.getBacklog());
        }
    }

            开始是一个jdk版本的判断, 我们以jdk7以上为例, 看到这条语句:

    javaChannel().bind(localAddress, config.getBacklog());

    终于找到了和jdk底层相关的绑定逻辑了, javaChannel()返回的是当前channel绑定的jdk底层的channel, 而bind()方法, 就是jdk底层的channel绑定端口的逻辑

    回到bind(final SocketAddress localAddress, final ChannelPromise promise)方法:

    首先看if判断: if (!wasActive && isActive()) 

    这里意思是如果之前不是active, 绑定之后是active的话, 执行if块, 显然这里符合条件, 继续往里走

    最终会走到这一步, pipeline.fireChannelActive()

    这也是传输active事件, 目前我们只需知道, 事件完成之后, 会调用AbstractChannel内部类AbstractUnsafe的beginRead()方法

    跟到AbstractUnsafe的beginRead()方法中:

    public final void beginRead() {
        assertEventLoop();
        if (!isActive()) {
            return;
        }
        try {
            doBeginRead();
        } catch (final Exception e) {
            //代码省略
        }
    }

    我们关注doBeginRead()方法:

    protected void doBeginRead() throws Exception {
        //拿到selectionKey
        final SelectionKey selectionKey = this.selectionKey;
        if (!selectionKey.isValid()) {
            return;
        }
        readPending = true;
        //获得感兴趣的事件
        final int interestOps = selectionKey.interestOps();
        //判断是不是对任何事件都不监听
        if ((interestOps & readInterestOp) == 0) {
            //此条件成立
            //将之前的accept事件注册, readInterest代表可以读取一个新连接的意思
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }

    这里到了jdk底层的调用逻辑, 通过注释不难看出其中的逻辑, 我们拿到和channel绑定的jdk底层的selectionKey, 获取其监听事件, 一上节我们知道, channel注册的时候没有注册任何事件, 所以我们这里if  ((interestOps & readInterestOp) == 0) 返回true, 之后, 将accept事件注册到channel中, 也就是 selectionKey.interestOps(interestOps | readInterestOp) 这步执行的

    注册完accept事件之后, 就可以轮询selector, 监听是否有新连接接入了

    第一章总结

            通过了这一章的学习, 我们了解了server启动的大概流程, 这里重点掌握整个启动脉络, 知道关键步骤在哪个类执行, 后面的章节会分析每一个模块的含义

     

    上一节: 注册多路复用

    下一节: NioEventLoopGroup之创建线程执行器

     

     

  • 相关阅读:
    Wolfram常用计算
    soapUI接口关联教程
    时间序列预测线上充值数据
    基于MySQL分析线上充值留存率
    更改用户资料需要完善脚本
    MySQL建立RFM模型
    Scrcpy使用入门
    虾皮Shopee社招面经分享(大数据开发方向),附内推方式
    MySQL Binlog 解析工具 Maxwell 详解
    MySQL Binlog 介绍
  • 原文地址:https://www.cnblogs.com/xiangnan6122/p/10202867.html
Copyright © 2020-2023  润新知