netty源码阅读之服务器启动之端口绑定

端口绑定,大部分是在AbstractChannel的内部类AbstractUnsafe的bind()这个方法里面完成的。主要完成了两件事情:

1、doBind()最终调用到javaChannel.bind()调用jdk底层绑定端口。

2、pipeline的channelActive传播事件,最终会调用到HeadContext.readIfIsAutoRead()(这个方法的作用是把原来注册到selector上面的事件重新绑定为accept事件,这样子有新连接进来netty就会接受这个时间并交给netty处理)。

在AbstractBoostrap的doBind()这个方法的initAndRegister()调用后面,有一个doBind0(),一层一层进去,到达AbstractChannelHeadContext的这个方法: 

    private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
        if (invokeHandler()) {
            try {
                ((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
            } catch (Throwable t) {
                notifyOutboundHandlerException(t, promise);
            }
        } else {
            bind(localAddress, promise);
        }
    }

点击HeadContext的实现,再进去一层就能看到AbstractUnsafe的bind的实现:

 public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
            assertEventLoop();

            if (!promise.setUncancellable() || !ensureOpen(promise)) {
                return;
            }

            // See: https://github.com/netty/netty/issues/576
            if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
                localAddress instanceof InetSocketAddress &&
                !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
                !PlatformDependent.isWindows() && !PlatformDependent.isRoot()) {
                // Warn a user about the fact that a non-root user can't receive a
                // broadcast packet on *nix if the socket is bound on non-wildcard address.
                logger.warn(
                        "A non-root user can't receive a broadcast packet if the socket " +
                        "is not bound to a wildcard address; binding to a non-wildcard " +
                        "address (" + localAddress + ") anyway as requested.");
            }

            boolean wasActive = isActive();
            try {
                doBind(localAddress);
            } catch (Throwable t) {
                safeSetFailure(promise, t);
                closeIfClosed();
                return;
            }

            if (!wasActive && isActive()) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireChannelActive();
                    }
                });
            }

            safeSetSuccess(promise);
        }

 

看NioServerSocketChannel的doBInd的实现:

 @Override
    protected void doBind(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) {
            javaChannel().bind(localAddress, config.getBacklog());
        } else {
            javaChannel().socket().bind(localAddress, config.getBacklog());
        }
    }

就是调用jdk底层实现端口绑定了。

观察到AbstractUnsafe的doBind后这一段:

if (!wasActive && isActive()) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireChannelActive();
                    }
                });
            }

wasActive实在doBind()之前赋值,这段代码的意思是:如果doBInd之前isActive为false,之后isActive()为true,那么就传播事件。也就是,之前没有传播过,后面绑定端口完成并且成功之后,就开始传播。(正常情况下在doBind之前isActive为false)

一样的道理,我们层层进入fireChannelActive最后到达HeadContext的channelAcitve方法(因为传播事件是在pipeline里面完成,pipeline的第一个节点是HeadContext):

    @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            ctx.fireChannelActive();

            readIfIsAutoRead();
        }

这里又做了两件事:

1、传播事件

2、把accept事件注册上去。

 

在传播事件这件事情上,我们一层一层点进去,在AbstractChannelContextHandler的这个方法里面:

    private void invokeChannelActive() {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelActive(this);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelActive();
        }
    }

channelActive这个方法,找我们用户代码自己实现的channelActive,就能看到这个:

 @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("channelActive");
    }

是不是很神奇!

 

在把accept事件注册上去这里,点击进去readIfIsAutoRead()这个方法,还是实现HeadContext的read方法,AbstractNioChannel的doBegainRead()方法:

  @Override
    protected void doBeginRead() throws Exception {
        // Channel.read() or ChannelHandlerContext.read() was called
        final SelectionKey selectionKey = this.selectionKey;
        if (!selectionKey.isValid()) {
            return;
        }

        readPending = true;

        final int interestOps = selectionKey.interestOps();
        if ((interestOps & readInterestOp) == 0) {
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }

首先我们看final int interestOps = selectionKey.interestOps();这段代码出来什么?

在我们把jdk底层的channel注册到selector上的时候,我们并不关心什么事件,之前的文章讲过,所以这里的interestOps是0。

我们看看readInterestOp从哪里来。在这里我们首先明确的是,这里的read并不是指“读事件”,而是“读到的事件”,这个“读到的事件”有可能是读事件、写事件、accept事件。其实这个readInterestOp是从NioServerSocketChannel这个类的构造函数来的,之前我们可能有点印象,在创建NioServerSocketChannel的时候:

    /**
     * Create a new instance using the given {@link ServerSocketChannel}.
     */
    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }

也就是这个readInterestOp就是SelectionKey.OP_ACCEPT。

那么  (interestOps & readInterestOp) == 0这段代码是什么意思呢?先这么理解,如果之前没有注册readInterestOp这样事件,就为false。结合下面这一段:interestOps | readInterestOp,这里指前面已经有注册一些事件,在那些事件的基础上,把readInterestOp(在这里是accept事件)这个事件也加上去。结合整个下面一段代码:
 

if ((interestOps & readInterestOp) == 0) {
            selectionKey.interestOps(interestOps | readInterestOp);
        }

如果之前没有注册过readInterestOp这样的事件,就把readInteresOp这样的事件注册进去(添加进去),并且之前的事件不受影响。

绑定成功之后就要告诉selector它需要关心accept事件,selector下次有这个事件就会交给netty处理。

我们再次来理解一下doBegainRead 的read,read就是可读了,读什么,在这里就是读accept事件,也就是新连接。

 

回顾一下netty服务端启动的几个过程:创建channel(创建jdk底层channel)、init初始化(最主要是添加连接器serverBootstrapAcceptor)、register(把第一步jdk底层创建channel注册到selector上面,并且把这个NioServerSocketChannel作为attachment添加进去),doBind(绑定端口,实现监听accept事件)

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值