【Netty】Netty 程序引导类(九)

news2024/11/30 2:27:10

文章目录

  • 前言
  • 一、引导程序类
  • 二、AbstractBootStrap 抽象类
  • 三、Bootstrap 类
  • 四、ServerBootstrap 类
  • 五、引导服务器
    • 5.1、 实例化引导程序类
    • 5.2、设置 EventLoopGroup
    • 5.3、指定 Channel 类型
    • 5.4、指定 ChannelHandler
    • 5.5、设置 Channel 选项
    • 5.6、绑定端口启动服务
  • 六、引导客户端
    • 6.1、实例化引导程序类
    • 6.2、设置 EventLoopGroup
    • 6.3、指定 Channel 类型
    • 6.4、设置 Channel 选项
    • 6.5、指定 ChannelHandler
    • 6.6、连接到服务器
  • 总结

前言

回顾Netty系列文章:

  • Netty 概述(一)
  • Netty 架构设计(二)
  • Netty Channel 概述(三)
  • Netty ChannelHandler(四)
  • ChannelPipeline源码分析(五)
  • 字节缓冲区 ByteBuf (六)(上)
  • 字节缓冲区 ByteBuf(七)(下)
  • Netty 如何实现零拷贝(八)

程序引导类(Bootstrap)可以理解为是一个程序的入口程序,在 Java 程序中,就是一个包含 main 方法的程序类。在 Netty 中,引导程序还包含一系列的配置项。本篇文章我们就来介绍 Netty 的引导程序。

一、引导程序类

引导程序类是一种引导程序,使 Netty 程序可以很容易地引导一个 Channel。在 Netty 中,承担引导程序的是 AbstractBootStrap抽象类。

引导程序类都在io.netty.bootstrap包下。AbstractBootStrap抽象类有两个子类:Bootstrap和ServerBootstrap,分别用于引导客户端程序及服务的程序。

下图展示了引导程序类的关系:
在这里插入图片描述
从上图可以看出,AbstractBootStrap抽象类实现了Cloneable接口。那么为什么需要实现Cloneable接口呢?

二、AbstractBootStrap 抽象类

在 Netty 中经常需要创建多个具有类似配置或者完全相同配置的Channel。为了支持这种模式而又不避免为每个Channel都创建并配置一个新的引导类实例,因此AbstractBootStrap被标记为了Cloneable。在一个已经配置完成的引导实例上调用clone()方法将返回另一个可以立即使用的引导类实例。

这种方式只会创建引导类实例的EventLoopGroup的一个浅拷贝,所以,EventLoopGroup将在所有克隆的Channel实例之间共享。这是可以接受的,毕竟这些克隆的Channel的生命周期都很短暂,例如,一个典型的场景是创建一个Channel以进行一次HTTP请求。

以下是AbstractBootStrap的核心源码:

public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {

    volatile EventLoopGroup group;
    private volatile ChannelFactory<? extends C> channelFactory;
    private volatile SocketAddress localAddress;
    private final Map<ChannelOption<?>, Object> options = new LinkedHashMap();
    private final Map<AttributeKey<?>, Object> attrs = new ConcurrentHashMap();
    private volatile ChannelHandler handler;

    AbstractBootstrap() {
        //禁止从其他程序包扩展
    }

    AbstractBootstrap(AbstractBootstrap<B, C> bootstrap) {
        this.group = bootstrap.group;
        this.channelFactory = bootstrap.channelFactory;
        this.handler = bootstrap.handler;
        this.localAddress = bootstrap.localAddress;
        synchronized(bootstrap.options) {
            this.options.putAll(bootstrap.options);
        }

        this.attrs.putAll(bootstrap.attrs);
    }
 
    //...
}

从上述源码可以看出,子类型B是其父类型的一个类型参数,因此可以返回到运行时实例的引用以支持方法的链式调用。从私有变量可以看出,AbstractBootStrap所要管理的启动配置包括EventLoopGroup、SocketAddress、Channel配置、ChannelHandler等信息。

AbstractBootStrap是禁止被除io.netty.bootstrap包外其他程序所扩展的,因此可以看到AbstractBootStrap默认构造方法被设置为了包内可见。

三、Bootstrap 类

BootStrap 类是AbstractBootStrap抽象类的子类之一,主要是用于客户端或者使用了无连接协议的应用程序。

以下是BootStrap 类的核心源码:

public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
    private static final InternalLogger logger = InternalLoggerFactory.getInstance(Bootstrap.class);
    private static final AddressResolverGroup<?> DEFAULT_RESOLVER;
    private final BootstrapConfig config = new BootstrapConfig(this);
    private volatile AddressResolverGroup<SocketAddress> resolver;
    private volatile SocketAddress remoteAddress;

    public Bootstrap() {
        this.resolver = DEFAULT_RESOLVER;
    }

    private Bootstrap(Bootstrap bootstrap) {
        super(bootstrap);
        this.resolver = DEFAULT_RESOLVER;
        this.resolver = bootstrap.resolver;
        this.remoteAddress = bootstrap.remoteAddress;
    }

    public Bootstrap resolver(AddressResolverGroup<?> resolver) {
        this.resolver = resolver == null ? DEFAULT_RESOLVER : resolver;
        return this;
    }

    public Bootstrap remoteAddress(SocketAddress remoteAddress) {
        this.remoteAddress = remoteAddress;
        return this;
    }

    public Bootstrap remoteAddress(String inetHost, int inetPort) {
        this.remoteAddress = InetSocketAddress.createUnresolved(inetHost, inetPort);
        return this;
    }

    public Bootstrap remoteAddress(InetAddress inetHost, int inetPort) {
        this.remoteAddress = new InetSocketAddress(inetHost, inetPort);
        return this;
    }

    public ChannelFuture connect() {
        this.validate();
        SocketAddress remoteAddress = this.remoteAddress;
        if (remoteAddress == null) {
            throw new IllegalStateException("remoteAddress not set");
        } else {
            return this.doResolveAndConnect(remoteAddress, this.config.localAddress());
        }
    }

    public ChannelFuture connect(String inetHost, int inetPort) {
        return this.connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
    }

    public ChannelFuture connect(InetAddress inetHost, int inetPort) {
        return this.connect(new InetSocketAddress(inetHost, inetPort));
    }

    public ChannelFuture connect(SocketAddress remoteAddress) {
        ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
        this.validate();
        return this.doResolveAndConnect(remoteAddress, this.config.localAddress());
    }

    public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
        ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
        this.validate();
        return this.doResolveAndConnect(remoteAddress, localAddress);
    }

	//...

    public Bootstrap validate() {
        super.validate();
        if (this.config.handler() == null) {
            throw new IllegalStateException("handler not set");
        } else {
            return this;
        }
    }

    public Bootstrap clone() {
        return new Bootstrap(this);
    }

    public Bootstrap clone(EventLoopGroup group) {
        Bootstrap bs = new Bootstrap(this);
        bs.group = group;
        return bs;
    }

    public final BootstrapConfig config() {
        return this.config;
    }

    final SocketAddress remoteAddress() {
        return this.remoteAddress;
    }

    final AddressResolverGroup<?> resolver() {
        return this.resolver;
    }

  	...
}

上述方法主要分为以下几类:

  • group:设置用于处理Channel所有事件的EventLoopGroup。
  • channel:指定了 Channel的实现类。
  • localAddress:指定 Channel应该绑定到的本地地址。如果没有指定,则有操作系统创建一个随机的地址。或者,也可以通过bind()或者connect()方法指定localAddress。
  • option:设置ChannelOption,其将被应用到每个新创建的Channel的ChannelConfig。这些选项将会通过bind()或者connect()方法设置到Channel,配置的顺序与调用先后没有关系。这个方法在Channel已经被创建后再次调用就不会再起任何效果了。支持什么样的ChannelOption取决于所使用的Channel类型。
  • attr:指定新创建的Channel的属性值。这些属性值是通过bind()或者connect()方法设置到Channel。配置的顺序取决于调用的先后顺序。这个方法在Channel已经被创建后再次调用就不会再起任何效果了。
  • handler:设置被添加到ChannelPipeline以接收事件通知的ChannelHandler。
  • remoteAddress:设置远程地址。也可以通过connect()方法来指定它。
    clone:创建一个当前Bootstrap的克隆,其具有和原始的Bootstrap相同的设置信息。
  • connect:用于连接到远程节点并返回一个ChannelFuture,并将会在连接操作完成后接收到通知。
  • bind:绑定Channel并返回一个ChannelFuture,其将会在绑定操作完成之后接收到通知,在那之后必须调用Channel。

BootStrap类中许多方法都继承自AbstractBootstrap类。

四、ServerBootstrap 类

ServerBootstrap 类是AbstractBootStrap抽象类的子类之一,主要是用于引导服务器程序。
以下是 ServerBootstrap 类的源码:

public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
    private static final InternalLogger logger = InternalLoggerFactory.getInstance(ServerBootstrap.class);
    private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap();
    private final Map<AttributeKey<?>, Object> childAttrs = new ConcurrentHashMap();
    private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);
    private volatile EventLoopGroup childGroup;
    private volatile ChannelHandler childHandler;

    public ServerBootstrap() {
    }

    private ServerBootstrap(ServerBootstrap bootstrap) {
        super(bootstrap);
        this.childGroup = bootstrap.childGroup;
        this.childHandler = bootstrap.childHandler;
        synchronized(bootstrap.childOptions) {
            this.childOptions.putAll(bootstrap.childOptions);
        }

        this.childAttrs.putAll(bootstrap.childAttrs);
    }

    public ServerBootstrap group(EventLoopGroup group) {
        return this.group(group, group);
    }

    public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
        super.group(parentGroup);
        if (this.childGroup != null) {
            throw new IllegalStateException("childGroup set already");
        } else {
            this.childGroup = (EventLoopGroup)ObjectUtil.checkNotNull(childGroup, "childGroup");
            return this;
        }
    }

    public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value) {
        ObjectUtil.checkNotNull(childOption, "childOption");
        synchronized(this.childOptions) {
            if (value == null) {
                this.childOptions.remove(childOption);
            } else {
                this.childOptions.put(childOption, value);
            }

            return this;
        }
    }

    public <T> ServerBootstrap childAttr(AttributeKey<T> childKey, T value) {
        ObjectUtil.checkNotNull(childKey, "childKey");
        if (value == null) {
            this.childAttrs.remove(childKey);
        } else {
            this.childAttrs.put(childKey, value);
        }

        return this;
    }

    public ServerBootstrap childHandler(ChannelHandler childHandler) {
        this.childHandler = (ChannelHandler)ObjectUtil.checkNotNull(childHandler, "childHandler");
        return this;
    }

	//...

    public ServerBootstrap clone() {
        return new ServerBootstrap(this);
    }

    /** @deprecated */
    @Deprecated
    public EventLoopGroup childGroup() {
        return this.childGroup;
    }

    final ChannelHandler childHandler() {
        return this.childHandler;
    }

    final Map<ChannelOption<?>, Object> childOptions() {
        synchronized(this.childOptions) {
            return copiedMap(this.childOptions);
        }
    }

    final Map<AttributeKey<?>, Object> childAttrs() {
        return copiedMap(this.childAttrs);
    }

    public final ServerBootstrapConfig config() {
        return this.config;
    }
    
    //...
}

上述方法主要分为以下几类:

  • group:设置ServerBootstrap要用的EventLoopGroup。这个EventLoopGroup将用于ServerChannel和被接受的子Channel的 I/O 处理。
  • channel:设置将要被实例化的ServerChannel类。
  • localAddress:指定 ServerChannel应该绑定到的本地地址。如果没有指定,则有操作系统创建一个随机的地址。或者,也可以通过bind()方法指定localAddress。
  • option:指定要应用到新创建的ServerChannel的ChannelConfig的ChannelOption。这些选项将会通过bind()设置到Channel,在bind()方法被调用之后,设置或者改变ChannelOption将不会有任何效果。支持什么样的ChannelOption取决于所使用的Channel类型。
  • childOption:指定当子Channel被接受时,应用到子Channel的ChannelConfig的ChannelOption。所支持的ChannelOption取决于所使用的Channel类型。
  • attr:指定ServerChannel上的属性值。这些属性值是通过bind()或者connect()方法设置到Channel。在bind()方法被调用之后它们将不会有任何效果。
  • childAttr:将属性设置给已经被接受的子Channel。之后再次调用将不会有任何效果。
  • handler:设置被添加到ServerChannel的ChannelPipeline中的ChannelHandler。
  • childHandler:设置将被添加到已被接受的子Channel的ChannelPipeline中的ChannelHandler。
  • clone:克隆一个设置好原始的ServerBootstrap相同的ServerBootstrap。
  • bind:绑定ServerChannel并返回一个ChannelFuture,其将会在绑定操作完成之后接收到通知(带着成功或者失败的结果)。

ServerBootstrap类中许多方法都继承自AbstractBootstrap类。

五、引导服务器

为了能更好的理解引导程序,下面就以 Echo 协议的服务器的代码为例。核心代码如下:

// 多线程事件循环器
EventLoopGroup bossGroup = new NioEventLoopGroup(); // boss
EventLoopGroup workerGroup = new NioEventLoopGroup(); // worker

try {
    // 启动NIO服务的引导程序类
    ServerBootstrap b = new ServerBootstrap(); 

    b.group(bossGroup, workerGroup) // 设置EventLoopGroup
        .channel(NioServerSocketChannel.class) // 指明新的Channel的类型
        .childHandler(new EchoServerHandler()) // 指定ChannelHandler
        .option(ChannelOption.SO_BACKLOG, 128) // 设置的ServerChannel的一些选项
        .childOption(ChannelOption.SO_KEEPALIVE, true); // 设置的ServerChannel的子Channel的选项

    // 绑定端口,开始接收进来的连接
    ChannelFuture f = b.bind(port).sync(); 

    System.out.println("EchoServer已启动,端口:" + port);

    // 等待服务器 socket 关闭 。
    // 在这个例子中,这不会发生,但你可以优雅地关闭你的服务器。
    f.channel().closeFuture().sync();
} finally {

    // 优雅的关闭
    workerGroup.shutdownGracefully();
    bossGroup.shutdownGracefully();
}

引导 Netty 服务器主要分为几下几个步骤。

5.1、 实例化引导程序类

在上述代码中,首先是需要实例化引导程序类。由于是服务器端的程序,所以,实例化了一个ServerBootstrap。

5.2、设置 EventLoopGroup

设置ServerBootstrap的EventLoopGroup。上述服务器使用了两个NioEventLoopGroup,一个代表boss线程组,一个代表work线程组。

boss线程主要是接收客户端的请求,并将请求转发给work线程处理。

boss线程是轻量的,不会处理耗时的任务,因此可以承受高并发的请求。而真实的 I/O 操作都是由work线程在执行。

NioEventLoopGroup是支持多线程的,因此可以执行线程池的大小。如果没有指定,则 Netty 会指定一个默认的线程池大小,核心代码如下:

private static final int DEFAULT_EVENT_LOOP_THREADS;

    static {
        // 默认 EventLoopGroup 的线程数
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

        if (logger.isDebugEnabled()) {
            logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
        }
    }

    public NioEventLoopGroup() {
        // 如果不指定, nThreads = 0
        this(0);
    }

    /**
     * @see MultithreadEventExecutorGroup#MultithreadEventExecutorGroup(int, Executor, Object...)
     */
    protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        // 如果不指定(nThreads = 0),默认值是 DEFAULT_EVENT_LOOP_THREADS
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }

从上述源码可以看出,如果NioEventLoopGroup实例化时,没有指定线程数,则最终默认值是DEFAULT_EVENT_LOOP_THREADS,而默认值DEFAULT_EVENT_LOOP_THREADS是根据当前机子的CPU 处理的个数乘以 2 得出的。

5.3、指定 Channel 类型

channel()方法用于指定ServerBootstrap的Channel类型。在本例中,使用的是NioServerSocketChannel类型,代表了服务器是一个基于ServerSocketChannel的实现,使用基于 NIO 选择器的实现来接受新连接。

5.4、指定 ChannelHandler

childHandler用于指定ChannelHandler,以便处理Channel的请求。上述例子中,指定的是自定义的EchoServerHandler。

5.5、设置 Channel 选项

option和childOption方法,分别用于设置ServerChannel及ServerChannel的子Channel的选项。这些选项定义在ChannelOption类中,包含以下常量:

public class ChannelOption<T> extends AbstractConstant<ChannelOption<T>> {
    
    public static final ChannelOption<ByteBufAllocator> ALLOCATOR = valueOf("ALLOCATOR");
    public static final ChannelOption<RecvByteBufAllocator> RCVBUF_ALLOCATOR = valueOf("RCVBUF_ALLOCATOR");
    public static final ChannelOption<MessageSizeEstimator> MESSAGE_SIZE_ESTIMATOR = valueOf("MESSAGE_SIZE_ESTIMATOR");

    public static final ChannelOption<Integer> CONNECT_TIMEOUT_MILLIS = valueOf("CONNECT_TIMEOUT_MILLIS");
    /**
     * @deprecated Use {@link MaxMessagesRecvByteBufAllocator}
     * and {@link MaxMessagesRecvByteBufAllocator#maxMessagesPerRead(int)}.
     */
    @Deprecated
    public static final ChannelOption<Integer> MAX_MESSAGES_PER_READ = valueOf("MAX_MESSAGES_PER_READ");
    public static final ChannelOption<Integer> WRITE_SPIN_COUNT = valueOf("WRITE_SPIN_COUNT");
    /**
     * @deprecated Use {@link #WRITE_BUFFER_WATER_MARK}
     */
    @Deprecated
    public static final ChannelOption<Integer> WRITE_BUFFER_HIGH_WATER_MARK = valueOf("WRITE_BUFFER_HIGH_WATER_MARK");
    /**
     * @deprecated Use {@link #WRITE_BUFFER_WATER_MARK}
     */
    @Deprecated
    public static final ChannelOption<Integer> WRITE_BUFFER_LOW_WATER_MARK = valueOf("WRITE_BUFFER_LOW_WATER_MARK");
    public static final ChannelOption<WriteBufferWaterMark> WRITE_BUFFER_WATER_MARK =
            valueOf("WRITE_BUFFER_WATER_MARK");

    public static final ChannelOption<Boolean> ALLOW_HALF_CLOSURE = valueOf("ALLOW_HALF_CLOSURE");
    public static final ChannelOption<Boolean> AUTO_READ = valueOf("AUTO_READ");

    /**
     * If {@code true} then the {@link Channel} is closed automatically and immediately on write failure.
     * The default value is {@code true}.
     */
    public static final ChannelOption<Boolean> AUTO_CLOSE = valueOf("AUTO_CLOSE");

    public static final ChannelOption<Boolean> SO_BROADCAST = valueOf("SO_BROADCAST");
    public static final ChannelOption<Boolean> SO_KEEPALIVE = valueOf("SO_KEEPALIVE");
    public static final ChannelOption<Integer> SO_SNDBUF = valueOf("SO_SNDBUF");
    public static final ChannelOption<Integer> SO_RCVBUF = valueOf("SO_RCVBUF");
    public static final ChannelOption<Boolean> SO_REUSEADDR = valueOf("SO_REUSEADDR");
    public static final ChannelOption<Integer> SO_LINGER = valueOf("SO_LINGER");
    public static final ChannelOption<Integer> SO_BACKLOG = valueOf("SO_BACKLOG");
    public static final ChannelOption<Integer> SO_TIMEOUT = valueOf("SO_TIMEOUT");

    public static final ChannelOption<Integer> IP_TOS = valueOf("IP_TOS");
    public static final ChannelOption<InetAddress> IP_MULTICAST_ADDR = valueOf("IP_MULTICAST_ADDR");
    public static final ChannelOption<NetworkInterface> IP_MULTICAST_IF = valueOf("IP_MULTICAST_IF");
    public static final ChannelOption<Integer> IP_MULTICAST_TTL = valueOf("IP_MULTICAST_TTL");
    public static final ChannelOption<Boolean> IP_MULTICAST_LOOP_DISABLED = valueOf("IP_MULTICAST_LOOP_DISABLED");

    public static final ChannelOption<Boolean> TCP_NODELAY = valueOf("TCP_NODELAY");

    @Deprecated
    public static final ChannelOption<Boolean> DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION =
            valueOf("DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION");

    public static final ChannelOption<Boolean> SINGLE_EVENTEXECUTOR_PER_GROUP =
            valueOf("SINGLE_EVENTEXECUTOR_PER_GROUP");

}

5.6、绑定端口启动服务

bind()方法用于绑定端口,会创建一个Channel而后启动服务。
绑定成功后,返回一个ChannelFuture,以代表是一个异步的操作。在上述的例子里,使用的是sync()方法,以同步的方式来获取服务启动的结果。

六、引导客户端

为了能更好的理解引导程序,下面就以 Echo 协议的客户端的代码为例。核心代码如下:

// 配置客户端
EventLoopGroup group = new NioEventLoopGroup();
try {
    Bootstrap b = new Bootstrap();
    b.group(group)
        .channel(NioSocketChannel.class)
        .option(ChannelOption.TCP_NODELAY, true)
        .handler(new EchoClientHandler());

    // 连接到服务器
    ChannelFuture f = b.connect(hostName, portNumber).sync();

    Channel channel = f.channel();
    ByteBuffer writeBuffer = ByteBuffer.allocate(32);
    try (BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in))) {
        String userInput;
        while ((userInput = stdIn.readLine()) != null) {
            writeBuffer.put(userInput.getBytes());
            writeBuffer.flip();
            writeBuffer.rewind();

            // 转为ByteBuf
            ByteBuf buf = Unpooled.copiedBuffer(writeBuffer);

            // 写消息到管道
            channel.writeAndFlush(buf);

            // 清理缓冲区
            writeBuffer.clear();
        }
    } catch (UnknownHostException e) {
        System.err.println("不明主机,主机名为: " + hostName);
        System.exit(1);
    } catch (IOException e) {
        System.err.println("不能从主机中获取I/O,主机名为:" + hostName);
        System.exit(1);
    }
} finally {

    // 优雅的关闭
    group.shutdownGracefully();
}

引导 Netty 客户端主要分为几下几个步骤。

6.1、实例化引导程序类

在上述代码中,首先是需要实例化引导程序类。由于是客户端的程序,所以,实例化了一个Bootstrap。

6.2、设置 EventLoopGroup

设置Bootstrap的EventLoopGroup。不同于服务器,客户端只需要使用了一个NioEventLoopGroup。

6.3、指定 Channel 类型

channel()方法用于指定Bootstrap的Channel类型。在本例中,由于是客户端使用,使用的是NioSocketChannel类型,代表了客户端是一个基于SocketChannel的实现,使用基于 NIO 选择器的实现来发起连接请求。

6.4、设置 Channel 选项

option用于设置Channel的选项。这些选项定义在ChannelOption类中。

6.5、指定 ChannelHandler

Handler用于设置处理服务端请求的ChannelHandler。上述例子中,指定的是自定义的EchoClientHandler。

6.6、连接到服务器

connect()方法用于连接到指定的服务器的Channel。
连接成功后,返回一个ChannelFuture,以代表是一个异步的操作。在上述的例子里,使用的是sync()方法,以同步的方式来获取服务启动的结果。

总结

通过上述对引导程序类的介绍,相信大家对于服务端和客户端的引导程序以及一些配置项的都有了一定的了解。通过看源码,我们就能更加清楚的知道 Netty 服务端和客户端的启动的过程。下节我们来分析Netty的线程模型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/572234.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STL-reverse_iterator 反向迭代器

回顾 对于STL中的容器&#xff0c;迭代器(iterator)是很重要的部分&#xff0c;同时迭代器也是STL六大组件之一&#xff0c;在之前我们实现vector和list中&#xff0c;我们已经对于迭代器有了初步的认识&#xff0c;为什么设计迭代器&#xff1f; 就是为了能像数组中的指针一样…

虹科干货|创新求变:虹科Redis企业版数据库驱动金融实时业务

BDO调查显示&#xff0c;43%的金融企业正计划全力加速数字化转型&#xff0c;互联网巨头与金融科技初创公司正在颠覆传统。”与此同时&#xff0c;客户行为、消费习惯和期望持续变化&#xff0c;以客户为中心的快速金融服务已成趋势&#xff0c;企业微服务、云原生应用亟需一组…

【算法题解】30. 全排列的递归解法

这是一道 中等难度 的题 https://leetcode.cn/problems/permutations/ 题目 给定一个不含重复数字的数组 n u m s nums nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1…

景点解说二维码怎么做?一键教你轻松生成二维码

现在的各种景区为了节省人工都会将景点的详细讲解做成二维码。通过手机扫码就能自助导览。那么&#xff0c;大家知道这种景区讲解二维码是怎么制作的吗&#xff1f; 一、什么工具能制作二维码图片&#xff1f; 机智熊二维码生成器&#xff08;https://www.jzx.com/&#xff09;…

什么是高性能计算实习生?做高性能计算有前景吗?

随着大模型和算力时代的大火&#xff0c;高性能计算实习的岗位越来越多了&#xff0c;各个大厂都在码人&#xff0c;百度、小米、字节、华为等等&#xff0c;也有很多网友晒出了面试一众知名芯片企业的面经和笔试题。 但是依然有很多朋友不清楚什么是高性能计算实习生&#xf…

搜索推荐系统[10]项目实战系列Z1:手把手教学(商品搜索系统、学术文献检索)语义检索系统搭建、召回排序模型详解。

搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术…

每日一练 | 网络工程师软考真题 Day12

阅读以下说明&#xff0c;答复以下【问题1】至【问题3】 【说明】 某单位有1个总部和6个分部&#xff0c;各个部门都有自己的局域网。该单位申请了6个C类IP地址202.115.10.0/24~202.115.15.0/24&#xff0c;其中总部与分部4共用一个C类地址。现方案将这些部门用路由器互联&…

linuxOPS基础_操作系统概述

计算机发展史 第一台计算机是1946 年2 月14 日诞生日&#xff0c;第一台名称ENIAC。体积一间屋子的大小&#xff0c;重量高达28t。 第一代&#xff1a;1946 – 1958 > 12 年 &#xff08;电子管&#xff09; 第二代&#xff1a;1958 – 1964 > 6 年 &#xff08;晶体管…

VR数字展厅——助力商企实现数字化营销展示

近年来&#xff0c;随着元宇宙、虚拟现实等概念逐渐进入大众视野&#xff0c;VR虚拟展厅也慢慢的发展成为了一种新的展示形式。VR数字展厅可以将展示场景复刻在线上&#xff0c;不再受限于线下环境&#xff0c;随着VR全景技术的高速发展&#xff0c;虚拟展厅帮助商企实现更具创…

一起CPU很闲,load却很大的案例分析

1、软硬件环境 硬件&#xff1a; 飞腾E2000Q 平台 软件&#xff1a; linux 4.19.246 2、问题现象 系统在上电后&#xff0c;无意中发现系统的平均负载很大&#xff0c;数值显示远超过cpu的承载能力。心想也没有跑什么业务程序呀&#xff0c;吓得赶紧运行top命令&#xff0c;瞅…

无人车端到端驾驶模型概述

摘要&#xff1a; 通常&#xff0c;端到端驾驶模型使用一个深度神经网络来完成这种映射&#xff0c;网络的所有参数为联合训练而得。这种方法因它的简洁高效而引人关注。 引言 在搭建无人车时&#xff0c;我和小伙伴们的主要工作是建立一个驾驶模型。所谓的驾驶模型是控制无人…

idea模板配置

idea版本&#xff1a;2023.1 未设置模板的idea&#xff0c;新建类会自动生成类注释 格式如下&#xff1a; /*** author user* date 2023/5/20 0020 14:25*/ public class User {} 其中&#xff0c;user为当前用户名 这里&#xff0c;如果希望将类注释改写成如下&#xff0…

“卷”还是“躺平”?职场人如何在工作中找到价值感?

今天不谈技术&#xff0c;只谈进步。 曾经看过一个回答说“职场人最好的姿势是仰卧起坐”。 卷累的就躺&#xff0c;休息好了再继续卷&#xff0c;卷是常态&#xff0c;“仰卧起坐”也好&#xff0c;“卷的姿势”也好&#xff0c;都是在反复“卷起”的过程中寻找一些舒适和平衡…

Z-Library2023现状

网上基本上年年都会传出来Z-Library要被干掉的消息&#xff0c;我一直觉得&#xff0c;如果那真的发生了&#xff0c;会是人类的悲哀。 由于之前我存储的地址又挂了&#xff0c;所以紧急又寻找了一下。 1.朋友帮忙 朋友帮我搜了一下&#xff0c;发现有三个地址。 他说这第一个…

智能CAN/串口协议转换器LCNET Pro RS-232/485

智能CAN/串口协议转换器LCNET Pro RS-232/485提供一路RS-485、一路RS-232和一路CAN通道&#xff0c;实现CAN与串口RS-485或RS-232之间的双向数据智能转换。每个通道独立隔离&#xff0c;每路通道采用金升阳电源模块和信号隔离芯片实现2500VDC电气隔离&#xff0c;电源输入防反设…

基于Redis的Java分布式锁,接口并发处理,并发方案

Redis的分布式锁很多人都知道&#xff0c;比如使用Jedis的setNx、incr等方法都可以实现分布式锁的功能&#xff0c;但是Jedis需要自己管理连接池&#xff0c;就稍微麻烦一点。 今天介绍的是使用RedisTemplate切面编程自定义注解SPEL来实现分布式锁的功能&#xff0c;封装完成后…

Spring Cloud 容错机试 Hystrix 服务降级 RestTemplate:

Ribon的服务降级操作 雪崩效应&#xff1a; 如果短信服务炸了后面的所有服务就会起连锁反应造成全部服务挂掉&#xff0c;这就是雪崩效应&#xff0c;那么其实短信服务又不是我们主要业务&#xff0c;这个时候我们可以采用服务降级&#xff0c;服务降级就是暂时的把短信服务停…

java学习——ArrayList和泛型(学习记录)

学习资料来自菜鸟教程 ArrayList 类是一个可以动态修改的数组&#xff0c;与普通数组的区别就是它是没有固定大小的限制&#xff0c;我们可以添加或删除元素。 ArrayList 继承了 AbstractList &#xff0c;并实现了 List 接口。 ArrayList 类位于 java.util 包中&#xff0c;使…

事件驱动模型IO模型

什么是事件驱动模型&#xff1f; 事件驱动模型是一种计算机编程模型&#xff0c;它通过等待事件的触发&#xff0c;在事件被触发时执行对应的处理函数。这种模型下&#xff0c;程序不再按照严格的顺序执行命令&#xff0c;而是以事件为驱动进行执行。事件驱动模型更适合处理大…

[RSA议题分析] Finding Vulnerabilities through Static Analysis and Scripting

文章目录 简介议题分析发现漏洞 - 什么时候/为什么什么是漏洞挖掘漏洞价值 如何挖洞逆向工程环境从哪开始挑战 总结 简介 作者讲了挖漏洞的目标&#xff0c;和一些常用的挖漏洞的方法和如果你像现在开始挖掘二进制漏洞&#xff0c;那么你可以从memcpy开始。除此之外&#xff0…