Netty源码解析之ServerSocketChannel bind流程

阅读须知

  • Netty版本:4.1.14.Final
  • 文章中使用/* */注释的方法会做深入分析

正文

提起Channel,相信熟悉java网络编程的同学都不陌生,Channel是JDK NIO类库的重要组成部分,用于非阻塞的I/O操作,同样的,Netty也提供了自己的Channel实现,为什么Netty不使用JDK NIO原生的Channel而要自己实现新的Channel呢?我们引用《Netty权威指南》这本书中对这个问题的解答:

  1. JDK的SocketChannel和ServerSocketChannel没有统一的Channel接口供业务开发者使用,对于用户而言,没有统一的操作视图,使用起来并不方便。
  2. JDK的SocketChannel和ServerSocketChannel和主要职责就是网络I/O操作,由于他们是SPI接口,由具体的虚拟机厂家提供,所以通过集成SPI功能类来扩展其功能的难度很大,直接实现ServerSocketChannel和SocketChannel抽象类,其工作量和重新开发一个新的Channel功能类是差不多的。
  3. Netty的Channel需要能够跟Netty的整体架构融合在一起,例如I/O模型、基于ChannelPipeline的定制模型,以及基于元数据描述配置化的TCP参数等,这些JDK的SocketChannel和ServerSocketChannel都没有提供,需要重新封装。
  4. 自定义的Channel,功能实现更加灵活。

基于上述4个原因,Netty重新设计了Channel接口,并且给予了很多不同的实现。它的设计原理比较简单,但是功能却比较繁杂,主要的设计理念如下:

  1. 在Channel接口层,采用Facade模式进行统一封装,将网络I/O操作、网络I/O相关联的其他操作封装起来,统一对外提供。
  2. Channel接口的定义尽量大而全,为SocketChannel和ServerSocketChannel提供统一视图,由不同子类实现不同的功能,公共功能在抽象父类中实现,最大程度上实现功能和接口的重用。
  3. 具体实现采用聚和而非包含的方式,将相关的功能类聚和在Channel中,由Channel统一负责分配和调度,功能实现更加灵活。

Channel是与网络socket或能够进行I / O操作(如读取,写入,连接和绑定)的组件的纽带。

Channel的功能比较繁杂,我们在ServerBootstrap源码分析文章的开篇部分写了一个小的启动demo,demo中使用了NioServerSocketChannel,我们就以NioServerSocketChannel作为入口进行分析,首先我们来看NioServerSocketChannel的层次结构图:
这里写图片描述
构造函数:

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

这里的newSocket方法会返回JDK的ServerSocketChannel实例,实现为ServerSocketChannelImpl。
NioServerSocketChannel:

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

这里的javaChannel().socket()会调用JDK的ServerSocketChannelImpl的socket方法返回JDK的ServerSocket实例。
AbstractNioMessageChannel:

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

AbstractNioChannel:

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent);
    this.ch = ch;
    this.readInterestOp = readInterestOp;
    try {
        ch.configureBlocking(false);
    } catch (IOException e) {
        try {
            ch.close();
        } catch (IOException e2) {
            if (logger.isWarnEnabled()) {
                logger.warn(
                    "Failed to close a partially initialized socket.", e2);
            }
        }
        throw new ChannelException("Failed to enter non-blocking mode.", e);
    }
}

AbstractChannel:

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

我们在分析ServerBootstrap源码时看到了启动过程中对channel.bind方法的调用,我们来分析一下bind方法的实现:
AbstractChannel:

public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
    return pipeline.bind(localAddress, promise);
}

这里的pipeline也就是我们刚刚看到的在构造函数中初始化的DefaultChannelPipeline

public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
    return tail.bind(localAddress, promise);
}

这里的tail对象是一个ChannelHandlerContext接口的实例,它的作用是作为ChannelHandler和ChannelPipeline交互的上下文。
AbstractChannelHandlerContext:

public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
    if (localAddress == null) {
        throw new NullPointerException("localAddress");
    }
    if (isNotValidPromise(promise, false)) {
        return promise; // 如果promise不合法,取消
    }
    // 这里会取出第一个outbound为true的ChannelHandlerContext,初始化pipeline时初始化了两个ChannelHandlerContext
    // tail和head,tail的outbound属性赋值为false,head的outbound属性赋值为true,所以这里首次取出的是head
    final AbstractChannelHandlerContext next = findContextOutbound();
    // 获取线程组,这里获取到的是boss线程组
    EventExecutor executor = next.executor();
    // 判断当前线程是否正在事件循环中执行
    if (executor.inEventLoop()) {
        /* 绑定调用 */
        next.invokeBind(localAddress, promise);
    } else {
        safeExecute(executor, new Runnable() {
            @Override
            public void run() {
                /* 绑定调用 */
                next.invokeBind(localAddress, promise);
            }
        }, promise, null);
    }
    return promise;
}

AbstractChannelHandlerContext:

private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
    // 尽可能判断是否调用了ChannelHandler的handlerAdded方法
    // handlerAdded方法会在将ChannelHandler添加到上下文并准备好处理事件之后调用
    if (invokeHandler()) {
        try {
            /* 绑定 */
            ((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
        } catch (Throwable t) {
            // 失败通知监听器
            notifyOutboundHandlerException(t, promise);
        }
    } else {
        bind(localAddress, promise); // 递归
    }
}

DefaultChannelPipeline.HeadContext:

public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) 
    throws Exception {
    unsafe.bind(localAddress, promise);
}

这里的unsafe来自于channel的unsafe。示例中我们使用的NioServerSocketChannel的unsafe为NioMessageUnsafe实例。
AbstractChannel.AbstractUnsafe:

public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
    // 断言Channel未注册并且当前线程正在事件循环中执行
    assertEventLoop();
    if (!promise.setUncancellable() || !ensureOpen(promise)) {
        return;
    }
    if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
        localAddress instanceof InetSocketAddress &&
        !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
        !PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {
        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.");
    }
    // 判断Channel的活跃状态,示例中使用的NioServerSocketChannel调用的是JDK的ServerSocket的isBound方法判断绑定状态
    boolean wasActive = isActive();
    try {
        /* 绑定操作 */
        doBind(localAddress);
    } catch (Throwable t) {
        // 异常将指定的promise标记为失败
        safeSetFailure(promise, t);
        // 如果Channel未开启,则关闭Channel
        closeIfClosed();
        return;
    }
    // 判断Channel是否是在绑定后变为活跃状态
    if (!wasActive && isActive()) {
        invokeLater(new Runnable() {
            @Override
            public void run() {
                // Channel激活,调用ChannelInboundHandler的channelActive方法
                pipeline.fireChannelActive();
            }
        });
    }
    // 将指定的promise标记为成功
    safeSetSuccess(promise);
}

Channel关闭流程详见:Netty源码解析之ServerSocketChannel close流程,关于promise的操作我们会在Promise源码分析的文章中进行详细分析,示例中我们使用的是NioServerSocketChannel,我们来看NioServerSocketChannel对doBind方法的实现:

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

这里就是调用JDK原生的函数来实现绑定操作,java7以上的版本调用我们上文提到的ServerSocketChannelImpl的bind方法实现,java7以下的版本调用ServerSocket的bind方法实现,bind操作会将channel的socket绑定到本地地址,并配置socket以侦听连接。到这里,bind流程的源码分析就完成了。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值