前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Netty Review - NioServerSocketChannel源码分析

Netty Review - NioServerSocketChannel源码分析

作者头像
小小工匠
发布2024-05-26 12:17:02
490
发布2024-05-26 12:17:02
举报
文章被收录于专栏:小工匠聊架构小工匠聊架构

概述


入口

代码语言:javascript
复制
ChannelFuture channelFuture = serverBootstrap.bind(9000).sync();

我们进入bind方法

代码语言:javascript
复制
/**
 * Create a new {@link Channel} and bind it.
 */
public ChannelFuture bind(int inetPort) {
    // 调用bind方法,并传入一个InetSocketAddress对象,其中端口号由参数inetPort指定
    return bind(new InetSocketAddress(inetPort));
}

这个方法的作用是创建一个新的Channel并绑定到指定的端口。它首先创建一个InetSocketAddress对象,该对象包含了要绑定的端口号,然后调用bind方法将新创建的Channel绑定到该InetSocketAddress。

这个方法的参数inetPort表示要绑定的端口号。通过调用bind(int inetPort)方法,可以方便地将新创建的Channel绑定到指定的端口。


final ChannelFuture regFuture = initAndRegister();

继续跟进去会进到 io.netty.bootstrap.AbstractBootstrap#doBind

代码语言:javascript
复制
    private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();
 		......
 		......      
}

我们重点关注 initAndRegister

代码语言:javascript
复制
这段代码是NioServerSocketChannel类中的一个私有方法,用于初始化并注册一个新的Channel。让我们逐步解释它:

```java
final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        // 使用ChannelFactory创建一个新的Channel实例
        channel = channelFactory.newChannel();
        // 初始化Channel,包括设置Channel的配置、分配Channel的ID等
        init(channel);
    } catch (Throwable t) {
        if (channel != null) {
            // 如果Channel已经创建但是初始化过程中发生了异常,则强制关闭Channel
            channel.unsafe().closeForcibly();
            // 创建一个新的ChannelPromise并设置异常
            return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
        }
        // 如果Channel还未创建就发生了异常,则创建一个FailedChannel实例,并设置异常
        return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
    }

    // 将Channel注册到EventLoopGroup中,并获取注册结果
    ChannelFuture regFuture = config().group().register(channel);
    // 如果注册过程中出现了异常
    if (regFuture.cause() != null) {
        // 如果Channel已经注册成功,直接关闭Channel
        if (channel.isRegistered()) {
            channel.close();
        } else {
            // 如果Channel尚未注册成功,强制关闭Channel
            channel.unsafe().closeForcibly();
        }
    }

    // 返回注册结果
    return regFuture;
}

这个方法的作用是初始化并注册一个新的Channel。它首先使用ChannelFactory创建一个新的Channel实例,然后调用init方法对其进行初始化。如果在初始化过程中发生了异常,则会强制关闭Channel,并返回一个带有异常的ChannelPromise。

接着,它将新创建的Channel注册到指定的EventLoopGroup中,并获取注册结果。如果注册过程中出现了异常,则会根据Channel是否已经注册成功来决定是直接关闭Channel还是强制关闭Channel。最后,返回注册结果。

这个方法的设计使得在初始化和注册Channel的过程中能够处理各种异常情况,并保证Channel的状态正确。


我们重点关注 channelFactory.newChannel();

来会看一下


那就继续 NioServerSocketChannel的构造函数 ,这里就是 NioServerSocketChannel初始化的地方 。


NioServerSocketChannel 类继承关系


实例化过程中做的事情

代码语言:javascript
复制
    @Override
    public T newChannel() {
        try {
            return constructor.newInstance();
        } catch (Throwable t) {
            throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
        }
    }

从上面的源码中,我们可以知道必然是无参构造函数 。

无参构造函数

代码语言:javascript
复制
/**
 * Create a new instance
 */
public NioServerSocketChannel() {
    // 调用另一个构造函数,传入一个新创建的Java NIO ServerSocketChannel实例
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

这段代码是NioServerSocketChannel类的构造函数,它调用了另一个私有构造函数来创建一个新的NioServerSocketChannel实例。

这个构造函数的作用是创建一个新的NioServerSocketChannel实例。它通过调用另一个私有构造函数来完成实例的创建过程。在调用私有构造函数时,传入了一个新创建的Java NIO ServerSocketChannel实例作为参数。

这样,NioServerSocketChannel实例就会持有这个Java NIO ServerSocketChannel实例,并在需要时对其进行操作。


`newSocket

我们继续看下 newSocket

代码语言:javascript
复制
private static ServerSocketChannel newSocket(SelectorProvider provider) {
    try {
        // 使用指定的SelectorProvider打开一个新的ServerSocketChannel  (熟悉的NIO代码)
        return provider.openServerSocketChannel();
    } catch (IOException e) {
        // 如果发生异常,则抛出ChannelException异常
        throw new ChannelException("Failed to open a server socket.", e);
    }
}
代码语言:javascript
复制
 private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();

这段代码是NioServerSocketChannel类中的一个私有静态方法,用于使用指定的SelectorProvider创建一个新的ServerSocketChannel。

这个方法的作用是使用指定的SelectorProvider打开一个新的ServerSocketChannel。在Java NIO中,SelectorProvider用于提供新的SelectableChannel实例。方法首先调用指定的SelectorProvider的openServerSocketChannel()方法来创建一个新的ServerSocketChannel实例。如果创建过程中发生了IO异常,则会捕获并抛出ChannelException异常。

provider.openServerSocketChannel() 熟悉的NIO代码


this(newSocket(DEFAULT_SELECTOR_PROVIDER));

代码语言:javascript
复制
/**
 * Create a new instance using the given {@link ServerSocketChannel}.
 */
public NioServerSocketChannel(ServerSocketChannel channel) {
    // 调用父类构造函数,传入null作为EventLoopGroup参数(因为NioServerSocketChannel没有父类EventLoopGroup),传入给定的ServerSocketChannel实例以及OP_ACCEPT作为感兴趣的事件
    super(null, channel, SelectionKey.OP_ACCEPT);
    // 创建一个新的NioServerSocketChannelConfig实例,用于配置NioServerSocketChannel的参数
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

这段代码是NioServerSocketChannel类的另一个构造函数,它接受一个ServerSocketChannel实例作为参数,并调用父类构造函数来初始化NioServerSocketChannel实例。

这个构造函数的作用是使用给定的ServerSocketChannel实例来创建一个新的NioServerSocketChannel实例。

在构造函数中,首先调用了父类AbstractNioMessageChannel的构造函数,传入了null作为EventLoopGroup参数(因为NioServerSocketChannel没有父类EventLoopGroup)、给定的ServerSocketChannel实例以及OP_ACCEPT作为感兴趣的事件。

然后,创建一个新的NioServerSocketChannelConfig实例,用于配置NioServerSocketChannel的参数。

SelectionKey.OP_ACCEPT是Java NIO中SelectableChannel所支持的一种操作兴趣标志。在Netty中,它通常用于NioServerSocketChannel,用于指示对新连接请求的接受操作感兴趣。

具体来说,SelectionKey.OP_ACCEPT表示SelectableChannel对新连接请求的接受操作感兴趣。当有新的连接请求到达时,Selector会将该事件通知给对应的SelectableChannel,并在之后的事件循环中处理该连接请求。在Netty中,NioServerSocketChannel通常会注册SelectionKey.OP_ACCEPT事件,以便及时响应新的连接请求。

通过使用这个操作兴趣标志,Netty能够利用Java NIO的非阻塞IO特性,实现高效的TCP服务器端编程,能够处理大量并发的连接请求,提高系统的性能和并发能力。


super(null, channel, SelectionKey.OP_ACCEPT)

这段代码是AbstractNioChannel类的受保护构造函数,用于创建一个新的AbstractNioChannel实例。让我们逐步解释它:

代码语言:javascript
复制
/**
 * Create a new instance
 *
 * @param parent            the parent {@link Channel} by which this instance was created. May be {@code null}
 * @param ch                the underlying {@link SelectableChannel} on which it operates
 * @param readInterestOp    the ops to set to receive data from the {@link SelectableChannel}
 */
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    // 调用父类构造函数,传入父Channel实例
    super(parent);
    // 初始化成员变量,保存SelectableChannel和读取操作的兴趣操作
    this.ch = ch;
    this.readInterestOp = readInterestOp;
    try {
        // 将SelectableChannel设置为非阻塞模式  (熟悉的NIO方法)
        ch.configureBlocking(false);
    } catch (IOException e) {
        try {
            // 发生异常时关闭SelectableChannel
            ch.close();
        } catch (IOException e2) {
            if (logger.isWarnEnabled()) {
                logger.warn("Failed to close a partially initialized socket.", e2);
            }
        }
        // 抛出ChannelException异常
        throw new ChannelException("Failed to enter non-blocking mode.", e);
    }
}

这个构造函数用于创建一个新的AbstractNioChannel实例。

它接受三个参数:

  • 父Channel、
  • 底层的SelectableChannel
  • 读取数据时的操作兴趣标志。

在构造函数内部,首先调用了父类构造函数,将父Channel传入以便建立关系。然后,初始化了成员变量,保存了SelectableChannel和读取操作的兴趣标志。

接着,尝试将SelectableChannel设置为非阻塞模式。如果设置过程中发生IO异常,会关闭SelectableChannel并抛出ChannelException异常。


super(parent);

这段代码是AbstractChannel类的受保护构造函数,用于创建一个新的AbstractChannel实例。让我们逐步解释它:

代码语言:javascript
复制
/**
 * Creates a new instance.
 *
 * @param parent
 *        the parent of this channel. {@code null} if there's no parent.
 */
protected AbstractChannel(Channel parent) {
    // 将父Channel赋值给成员变量parent
    this.parent = parent;
    // 生成唯一的通道ID
    id = newId();
    // 创建一个新的Unsafe实例,用于执行底层操作
    unsafe = newUnsafe();
    // 创建一个新的ChannelPipeline实例,用于存储和处理ChannelHandler
    pipeline = newChannelPipeline();
}

这个构造函数用于创建一个新的AbstractChannel实例。它接受一个参数parent,用于指定这个Channel的父Channel。

在构造函数内部,首先将父Channel赋值给成员变量parent,然后生成一个唯一的通道ID,接着创建一个新的Unsafe实例用于执行底层操作,最后创建一个新的ChannelPipeline实例用于存储和处理ChannelHandler。

newChannelPipeline();创建一个新的ChannelPipeline实例,用于存储和处理ChannelHandler

代码语言:javascript
复制
/**
 * Returns a new {@link DefaultChannelPipeline} instance.
 */
protected DefaultChannelPipeline newChannelPipeline() {
    // 创建一个新的DefaultChannelPipeline实例,传入当前的Channel作为参数
    return new DefaultChannelPipeline(this);
}

这段代码是AbstractChannel类中的一个受保护方法,用于创建一个新的DefaultChannelPipeline实例。

这个方法用于创建一个新的DefaultChannelPipeline实例,并将当前的Channel作为参数传入。DefaultChannelPipeline是Netty中用于管理ChannelHandler链的默认实现。每个Channel都有自己的ChannelPipeline,用于存储和处理ChannelHandler。调用这个方法会创建一个新的ChannelPipeline实例,并将当前的Channel作为其所属的Channel。

代码语言:javascript
复制
protected DefaultChannelPipeline(Channel channel) {
    // 将传入的Channel赋值给成员变量channel
    this.channel = ObjectUtil.checkNotNull(channel, "channel");
    // 创建一个新的SucceededChannelFuture实例,表示成功的Future
    succeededFuture = new SucceededChannelFuture(channel, null);
    // 创建一个新的VoidChannelPromise实例,表示空的Promise
    voidPromise =  new VoidChannelPromise(channel, true);

    // 创建一个新的TailContext实例,表示管道中的尾部节点
    tail = new TailContext(this);
    // 创建一个新的HeadContext实例,表示管道中的头部节点
    head = new HeadContext(this);

    // 设置头部节点的下一个节点为尾部节点,尾部节点的上一个节点为头部节点
    head.next = tail;
    tail.prev = head;
}

这段代码是DefaultChannelPipeline类的受保护构造函数,用于创建一个新的DefaultChannelPipeline实例。

这个构造函数用于创建一个新的DefaultChannelPipeline实例。在构造函数内部,首先将传入的Channel赋值给成员变量channel,并创建了一个成功的Future实例(succeededFuture)和一个空的Promise实例(voidPromise)。

接着创建了头部节点(HeadContext)和尾部节点(TailContext),并设置头部节点的下一个节点为尾部节点,尾部节点的上一个节点为头部节点。

这样就构成了一个空的ChannelPipeline链表结构。


ch.configureBlocking(false)

ch.configureBlocking(false);熟悉的NIO代码

ch.configureBlocking(false)是将一个Java NIO的SelectableChannel配置为非阻塞模式。

在Java NIO中,SelectableChannel是一个可以注册到Selector上并监听IO事件的通道,比如SocketChannelServerSocketChannel等。在默认情况下,这些通道都是阻塞模式的,意味着当没有数据可读或无法写入时,读取和写入操作会一直阻塞当前线程,直到有数据可用或者通道关闭。

通过调用configureBlocking(false)方法,可以将这些通道配置为非阻塞模式。在非阻塞模式下,当没有数据可读或无法写入时,读取和写入操作会立即返回而不会阻塞当前线程,这样就可以在单个线程上处理多个通道的IO操作,提高了系统的并发处理能力。

在Netty中,通常会将SelectableChannel配置为非阻塞模式,以利用Java NIO的非阻塞IO特性,实现高效的事件驱动的网络编程。


小结

NioServerSocketChannel是Netty中用于处理TCP服务器端Socket的通道实现之一。它继承自AbstractNioMessageChannel,是基于Java NIO的ServerSocketChannel的封装。以下是关于NioServerSocketChannel的总结:

  1. 构造函数
    • 有两个构造函数:一个接受ServerSocketChannel实例作为参数,另一个不接受任何参数。
    • 构造函数负责初始化NioServerSocketChannel实例,其中包括调用父类构造函数、配置SelectableChannel为非阻塞模式等操作。
  2. 初始化和注册
    • 使用initAndRegister方法初始化和注册NioServerSocketChannel。
    • 在初始化过程中,会调用channelFactory.newChannel()方法创建一个新的NioServerSocketChannel实例,并初始化该实例。
    • 如果初始化过程中出现异常,会尝试关闭部分初始化的SocketChannel,并返回一个包含异常信息的DefaultChannelPromise实例。
  3. 功能和特性
    • 用于接受TCP连接请求,并创建对应的NioSocketChannel实例来处理连接。
    • 可以设置Channel的各种参数,如TCP参数、选项等。
    • 通过继承自AbstractNioMessageChannel,具有处理读写事件的能力。
  4. 底层实现
    • 使用了Java NIO的ServerSocketChannel来处理TCP连接请求。
    • 通过配置SelectableChannel为非阻塞模式,实现了高性能的非阻塞IO操作。

综上所述,NioServerSocketChannel是Netty中用于处理TCP服务器端Socket的通道实现,它基于Java NIO,提供了高性能的非阻塞IO操作,并具有初始化、注册、配置参数等功能。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 入口
  • NioServerSocketChannel 类继承关系
  • 实例化过程中做的事情
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档