zoukankan      html  css  js  c++  java
  • Netty源码分析第1章(Netty启动流程)---->第2节: NioServerSocketChannel的创建

     

    Netty源码分析第一章:  Server启动流程

     

    第二节:NioServerSocketChannel的创建

     

    我们如果熟悉Nio, 则对channel的概念则不会陌生, channel在相当于一个通道, 用于数据的传输

    Netty将jdk的channel进行了包装, 并为其扩展了更多的功能

    在netty中也分为服务端channel和客户端channel, 在Nio模式下, 服务端channel对应的类为NioServerSocketChannel, 包装的jdk的ServerSocketChannel

    客户端channel对应的类为NioSocketChannel, 所包装的jdk的类为SocketChannel

    最简单的继承关系如下(经简化):

    1-2-1

     

    我们继续看第一小节demo:

    //创建boss和worker线程(1)
    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    //创建ServerBootstrap(2)
    ServerBootstrap b = new ServerBootstrap();
    //初始化boss和work线程化两个线程(3)
    b.group(bossGroup, workerGroup)
            //声明NioServerSocketChannel(4)
            .channel(NioServerSocketChannel.class)
            //初始化客户端Handler(5)
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) {
                    ch.pipeline().addLast(new StringDecoder());
                    ch.pipeline().addLast(new StringEncoder());
                    ch.pipeline().addLast(new ServerHandler());
                }
            });
    //绑定端口(6)
    ChannelFuture f = b.bind(8888).sync();
    f.channel().closeFuture().sync();

    我们继续看第六步, 绑定端口:

    ChannelFuture f = b.bind(8888).sync();

    在此, 我们看到绑定了8888端口

     

    我们跟到bind(8888)方法中:

    public ChannelFuture bind(int inetPort) {
        return bind(new InetSocketAddress(inetPort));
    }

    端口封装成socket地址对象

     

    继续跟bind方法:

    public ChannelFuture bind(SocketAddress localAddress) {
        validate();
        return doBind(localAddress);
    }

    validate()做了一些属性验证

     

    我们继续跟到doBind(localAddress)方法:

    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;
        }
    }

    去除了一些非关键的代码, 重点关注注释标注的第一步, 初始化并注册:

    final ChannelFuture regFuture = initAndRegister();

    我们跟进initAndRegister()方法:

    final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            //创建channel
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
            //忽略非关键代码
        }
        ChannelFuture regFuture = config().group().register(channel);
        //忽略非关键代码
        return regFuture;
    }

    关注添加注释的步骤, 创建channel, 这一点也是我们这节需要讲明白的内容

     

    看这一步:

    channel = channelFactory.newChannel();

    这里channelFactory调用了newChannel()的这个方法, 这个方法从名字就不难理解, 是新建一个channel, 回顾下上一小节, 这个channelFactory是在哪里初始化呢?

     

    根据上一小节代码, channelFactory是在Bootstrap的channelFactory ()方法初始化的:

    public B channelFactory(ChannelFactory<? extends C> channelFactory) {
        this.channelFactory = channelFactory;
        return (B) this;
    }

    而这个方法又是channel()方法中调用的:

    public B channel(Class<? extends C> channelClass) {
        return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
    }

    这里传入ReflectiveChannelFactory对象就是初始化的channelFactory对象

    所以newChannel()是调用ReflectiveChannelFactory对象的newChannel方法

     

    跟到ReflectiveChannelFactory对象的newChannel方法中:

    @Override
    public T newChannel() {
        try { 
            return clazz.newInstance();
        } catch (Throwable t) {
            throw new ChannelException("Unable to create Channel from class " + clazz, t);
        }
    }

    我们看到这个clazz对象通过反射创建了channel, 这个clazz对象, 就是我们上一节提到过的, 初始化的NioServerSocketChannel的class对象

    这里通过反射调用, 会创建一个NioServerSokectChannel

     

    学习过nio的小伙伴都知道jdk的ServerSocketChannel, 用于接受链接事件, 而netty的NioServerSocketChannel是和jdk的channel有什么关系呢?

     

    实际上netty的channel和jdk的channel的含义一样, 也是一个通道, 只是netty为其做了扩展, 而channel的事件处理, 也是通过jdk的channel去做的, 我们跟随着NioServerSocketChannel的创建过程, 来了解他们之间的关联关系

     

    clazz.newInstance()通过反射创建一个NioServerSocketChannel对象, 首先会走到NioServerSocketChannel的构造方法, 我们跟到他的构造方法, 查看NioServerSocketChannel的创建过程

    首先会调用它的无参构造方法:

    public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }

    我们看到这个构造方法调用了另一个有参的构造方法, 传入参数是 newSocket(DEFAULT_SELECTOR_PROVIDER) 

    我们首先看DEFAULT_SELECTOR_PROVIDER这个这变量:

    private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();

    看到这初始化了一个SelectorProvider对象, 而这个对象是通过静态方法provider()创建的, SelectorProvider对象可以用于创建jdk底层的ServerSocketChannel

     

    我们继续跟到newSocket(DEFAULT_SELECTOR_PROVIDER)中:

    private static ServerSocketChannel newSocket(SelectorProvider provider) {
        return provider.openServerSocketChannel();
    }

    去掉try-catch块, 发现这个方法是通过SelectorProvider对象的openServerSocketChannel()方法创建一个jdk底层的ServerSocketChannel, 至此我们可以知道, 与NioServerSokectChannel绑定的jdk底层的ServerSocketChannel就是这么创建的

     

    那么创建之后如何与netty的channel绑定?继续跟代码

    跟到this(newSocket(DEFAULT_SELECTOR_PROVIDER))中:

    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }

    我们看到这里调用了父类的构造方法,继续往里跟:

    protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent, ch, readInterestOp);
    }

    这里调用了其父类AbstractNioMessageChannel类的构造方法, AbstractNioMessageChannel这个类同学们请记住, 有关是NioServerSocketChannel的父类, 代表着服务端channel的相关属性和操作, 之后有关服务端channel的一些事件会在这个类中完成

    我们看到这个类的构造方法中又调用了它的父类的构造方法, 我们继续跟:

    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        //保存channel
        this.ch = ch;
        //绑定事件
        this.readInterestOp = readInterestOp;
        try {
            //设置为非阻塞
            ch.configureBlocking(false);
        } catch (IOException e) {
            //去掉非关键代码
        }
    }

    这里又调用了其父类AbstractChannel的构造方法, 跟进去这个方法之前, 我们先往下看

     

    首先看这一步:

    this.ch = ch;

    这步就是绑定jdk底层的ServerSocketChannel, 至此我们知道, jdk的channel和netty定义的channel是组合关系, netty的channel中有个jdk的channel的成员变量, 而这个成员变量就定义在AbstractNioChannel这个类当中, 希望同学们将这个结论牢牢记住, 对以后的学习很有帮助

     

    我们看到后面的这一步:

    ch.configureBlocking(false);

    这一步, 就是将jdk的channel设置为非阻塞模式, 这里熟悉Nio的同学应该不会陌生, 这里不再赘述

     

     我们继续跟到super(parent)中, 走到其父类AbstractChannel的构造方法:

    protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }

    首先看下这个parent, 这个parent是NioServerSocketChannel调用其父类构造方法传入的, 传入的是null, 所以这一步AbstractChannel的属性parent也是null, 这个parent, 我们之后再讲客户端channel的时候会讲到

    id = newId()是为每个channel创建一个唯一id

     

    我们重点关注下后两步:

    unsafe = newUnsafe();
    pipeline = newChannelPipeline();

    这里初始化了两个属性unsafe, 和pipeline, 目前我们只需要知道这两个属性是在这里初始化的, 至于这两个属性的概念, 后面的章节会讲到

    以上就是创建NioServerSocketChannel的过程, 同学们可以课后跟进源码去熟悉巩固

     

    上一节: 服务端初始化

    下一节: 服务端Channel的初始化

     

     

     

     

     

     

  • 相关阅读:
    五种实用DOM方法总结
    九月工作总结
    八月下半月工作总结
    【工作总结】七月底-八月中
    权限管理功能的实现
    JavaWeb项目出现红色感叹号
    项目中遇到的AngularJs问题
    五月工作总结
    生成流水号的优化
    AngularJS scope 作用域的问题
  • 原文地址:https://www.cnblogs.com/xiangnan6122/p/10202370.html
Copyright © 2011-2022 走看看