Netty核心线程模型源码分析

文章目录

    • 一、Netty线程模型简介
    • 二、Netty线程模型源码分析
      • 1. 服务端源码分析

一、Netty线程模型简介

Netty的线程模型图如下所示:
在这里插入图片描述
具体细节看这篇博客

二、Netty线程模型源码分析

1. 服务端源码分析

首先我们在写Netty服务端程序的时候最开始是下面两句代码:

//指定EvenLoopGroup中EvenLoop(线程数)的数量
EvenLoopGroup bossGroup = new NioEventLoopGroup(1);
//使用默认数量
EvenLoopGroup bossGroup = new NioEventLoopGroup();

我们进入默认构造方法:

//如果我们不传递参数,默认数就是0
 public NioEventLoopGroup() {
        this(0);
    }

protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args);
    }

上面代码最核心的是super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args);,这里面它判断参数传入的线程数nThreads是否为0,如果是0线程数就是默认的DEFAULT_EVENT_LOOP_THREADS,我们看看这个默认值是怎么初始化的:

 private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

上面代码的逻辑就是1和SystemPropertyUtil.getInt的返回值进行比较,这个方法会首先判断系统参数"io.netty.eventLoopThreads"是否配置了,配置了就用该系统参数的值,没有配置就使用NettyRuntime.availableProcessors() * 2也就是该机器核数的2倍。上面代码就设置了NioEventLoopGroup中线程数的数量。我们继续进入super方法。

 protected MultithreadEventExecutorGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, Object... args) {
        this.terminatedChildren = new AtomicInteger();
        this.terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE);
        //如果参数nThreads是0,就抛出异常(前面我们已经讲解了源码中这个参数是如何设置的)
        if (nThreads <= 0) {
            throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
        } else {
        	//我们知道在前面的代码中我们并没有指定线程池,这个if就是帮我们创建线程池
            if (executor == null) {
                executor = new ThreadPerTaskExecutor(this.newDefaultThreadFactory());
            }
			//这里又指定了一个线程池数组,然后数量就是参数nThreads(线程池数组)
            this.children = new EventExecutor[nThreads];

            int j;
            //遍历线程池数组中每一个线程池
            for(int i = 0; i < nThreads; ++i) {
                boolean success = false;
                boolean var18 = false;

                try {
                    var18 = true;
                    //给线程池赋值
                    this.children[i] = this.newChild((Executor)executor, args);
                    success = true;
                    var18 = false;
                } catch (Exception var19) {
                    throw new IllegalStateException("failed to create a child event loop", var19);
                } finally {
                    if (var18) {
                        if (!success) {
                            int j;
                            for(j = 0; j < i; ++j) {
                                this.children[j].shutdownGracefully();
                            }

                            for(j = 0; j < i; ++j) {
                                EventExecutor e = this.children[j];

                                try {
                                    while(!e.isTerminated()) {
                                        e.awaitTermination(2147483647L, TimeUnit.SECONDS);
                                    }
                                } catch (InterruptedException var20) {
                                    Thread.currentThread().interrupt();
                                    break;
                                }
                            }
                        }

                    }
                }

                if (!success) {
                    for(j = 0; j < i; ++j) {
                        this.children[j].shutdownGracefully();
                    }

                    for(j = 0; j < i; ++j) {
                        EventExecutor e = this.children[j];

                        try {
                            while(!e.isTerminated()) {
                                e.awaitTermination(2147483647L, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException var22) {
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }

            this.chooser = chooserFactory.newChooser(this.children);
            FutureListener<Object> terminationListener = new FutureListener<Object>() {
                public void operationComplete(Future<Object> future) throws Exception {
                    if (MultithreadEventExecutorGroup.this.terminatedChildren.incrementAndGet() == MultithreadEventExecutorGroup.this.children.length) {
                        MultithreadEventExecutorGroup.this.terminationFuture.setSuccess((Object)null);
                    }

                }
            };
            EventExecutor[] var24 = this.children;
            j = var24.length;

            for(int var26 = 0; var26 < j; ++var26) {
                EventExecutor e = var24[var26];
                e.terminationFuture().addListener(terminationListener);
            }

            Set<EventExecutor> childrenSet = new LinkedHashSet(this.children.length);
            Collections.addAll(childrenSet, this.children);
            this.readonlyChildren = Collections.unmodifiableSet(childrenSet);
        }
    }

首先我们先看上面代码的第一个核心代码:

 this.children[i] = this.newChild((Executor)executor, args);

//NIOEventLoopGroup的实现
 protected EventLoop newChild(Executor executor, Object... args) throws Exception {
        EventLoopTaskQueueFactory queueFactory = args.length == 4 ? (EventLoopTaskQueueFactory)args[3] : null;
        return new NioEventLoop(this, executor, (SelectorProvider)args[0], ((SelectStrategyFactory)args[1]).newSelectStrategy(), (RejectedExecutionHandler)args[2], queueFactory);
    }

从返回值我们看到类型是NioEventLoop,也就是说 this.children[i]这个线程池数组中放的是NioEventLoop对象,这也对应了线程模型图中的下面红圈部分:
在这里插入图片描述
我们继续进入NioEventLoop的构造函数:

 NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider, SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler, EventLoopTaskQueueFactory queueFactory) {
        super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory), rejectedExecutionHandler);
        if (selectorProvider == null) {
            throw new NullPointerException("selectorProvider");
        } else if (strategy == null) {
            throw new NullPointerException("selectStrategy");
        } else {
            this.provider = selectorProvider;
            SelectorTuple selectorTuple = this.openSelector();
            this.selector = selectorTuple.selector;
            this.unwrappedSelector = selectorTuple.unwrappedSelector;
            this.selectStrategy = strategy;
        }
    }

上面代码首先第一句是调用父类的构造方法:

  super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory), rejectedExecutionHandler);

 protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor, boolean addTaskWakesUp, Queue<Runnable> taskQueue, Queue<Runnable> tailTaskQueue, RejectedExecutionHandler rejectedExecutionHandler) {
        super(parent, executor, addTaskWakesUp, taskQueue, rejectedExecutionHandler);
        this.tailTasks = (Queue)ObjectUtil.checkNotNull(tailTaskQueue, "tailTaskQueue");
    }


    protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor, boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedHandler) {
        super(parent);
        this.threadLock = new CountDownLatch(1);
        this.shutdownHooks = new LinkedHashSet();
        this.state = 1;
        this.terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE);
        this.addTaskWakesUp = addTaskWakesUp;
        this.maxPendingTasks = Math.max(16, maxPendingTasks);
        this.executor = ThreadExecutorMap.apply(executor, this);
        //这个taskQueue的底层本质上就是一个LinkedBlockingQueue,阻塞队列
        this.taskQueue = this.newTaskQueue(this.maxPendingTasks);
        this.rejectedExecutionHandler = (RejectedExecutionHandler)ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
    }

继续回到NioEventLoop构造函数,然后是下面代码:

   this.provider = selectorProvider;
   SelectorTuple selectorTuple = this.openSelector();
   this.selector = selectorTuple.selector;

上面代码逻辑其实在Java的NIO中也有,就是获取一个Selector,然后赋值给了this.selector这个Ni oEventLoop成员变量。到这里NioEventLoop的两个核心组件Selector和TaskQueue就找到源头了,我们继续回到MultithreadEventExecutorGroup方法:

 this.children[i] = this.newChild((Executor)executor, args);

通过上面分析,我们知道了this.children[I]这个线程池数组里面封装的就是一个一个的NioEventLoop,然后NioEventLoop底层就是一个Selector一个TaskQueue,也正是对应了线程模型中的第一个部分,然后newChild((Executor)executor, args);这个方法的第一个参数就是executor它是我们前面创建的一个ThreadPerTaskExecutor线程池,这里先不介绍这个线程池。到此NioEventLoopGroup创建的大致的底层逻辑就分析完了,当然还有很多细节代码没有分析,这里就和后面具体的业务代码进行分析,在NioEventLoopGroup创建完成后,主程序就开始执行下面代码:

//指定EvenLoopGroup中EvenLoop(线程数)的数量
EvenLoopGroup bossGroup = new NioEventLoopGroup(1);
//使用默认数量
EvenLoopGroup bossGroup = new NioEventLoopGroup();
try{
	//创建服务器端的启动对象
	ServerBootStrap bootstrap =new ServerBootStrap();
}

点进ServerBootStrap的构造函数:

public ServerBootstrap() {
    }

可以发现它的构造方法中什么也没干,也就没什么逻辑分析了,继续执行主业务代码:

//指定EvenLoopGroup中EvenLoop(线程数)的数量
EvenLoopGroup bossGroup = new NioEventLoopGroup(1);
//使用默认数量
EvenLoopGroup bossGroup = new NioEventLoopGroup();
try{
	//创建服务器端的启动对象
	ServerBootStrap bootstrap =new ServerBootStrap();
	//使用链式编程来配置参数
	bootstrap.group(bossGroup,workerGroup)//设置两个线程组
}

ServerBootStrap的参数的配置通常使用链式编程的方式进行配置,首先看第一个配置.group(bossGroup,childGroup),从形式上看就是将我们前面定义的两个NiojEventLoopGroup设置进去了,我们进去源码看看:

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

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

从参数我们可以看出bossGroup对应的是parentGroup(父线程组),workerGroup对应的是childGroup(子线程组),首先调用了 super.group(parentGroup);即父类的group方法,父类的group方法主要就是将bossGroup赋值给了this.group 成员变量,然后childGroup赋值给子类的this.childGroup成员变量,然后返回this当前对象,达到了链式调用的条件。上面代码本质上就是将我们创建的NioEventLoopGroup对象保存到了ServerBootStrap对象中。到此group(bossGroup,workerGroup)方法就执行完了,继续回到主业务代码:

//指定EvenLoopGroup中EvenLoop(线程数)的数量
EvenLoopGroup bossGroup = new NioEventLoopGroup(1);
//使用默认数量
EvenLoopGroup bossGroup = new NioEventLoopGroup();
try{
	//创建服务器端的启动对象
	ServerBootStrap bootstrap =new ServerBootStrap();
	//使用链式编程来配置参数
	bootstrap.group(bossGroup,workerGroup)//设置两个线程组
			 .channel(NioServerSocketChannel.class) //使用NioServerSocketChannel作为服务器通道的实现
}

我们进入channel(NioServerSocketChannel.class) 方法:

   public B channel(Class<? extends C> channelClass) {
        return this.channelFactory((io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory((Class)ObjectUtil.checkNotNull(channelClass, "channelClass"))));
    }

按照代码逻辑就是调用了this.channelFactory函数,然后该函数的参数就是new ReflectiveChannelFactory((Class)ObjectUtil.checkNotNull(channelClass, "channelClass"))根据我们传入的channelClass类型创建了一个ReflectiveChannelFactory我们进入内部代码看看:

public ReflectiveChannelFactory(Class<? extends T> clazz) {
        ObjectUtil.checkNotNull(clazz, "clazz");

        try {
        	//将我们传入的channel类型的构造函数暂存了起来
            this.constructor = clazz.getConstructor();
        } catch (NoSuchMethodException var3) {
            throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) + " does not have a public non-arg constructor", var3);
        }
    }

上面方法就是将我们的指定的NioServerSocketChannel的构造函数保存到了ReflectiveChannelFactory对象中了,然后执行this.channelFactory方法,我们进入该方法:

  public B channelFactory(ChannelFactory<? extends C> channelFactory) {
        ObjectUtil.checkNotNull(channelFactory, "channelFactory");
        if (this.channelFactory != null) {
            throw new IllegalStateException("channelFactory set already");
        } else {
        	//这里就是将前面创建的channel工厂对象保存了起来
            this.channelFactory = channelFactory;
            return this.self();
        }
    }

上面代码就是将前面我们创建的ReflectiveChannelFactory保存到了 this.channelFactory这个成员变量中。然后回到主业务代码:

//指定EvenLoopGroup中EvenLoop(线程数)的数量
EvenLoopGroup bossGroup = new NioEventLoopGroup(1);
//使用默认数量
EvenLoopGroup bossGroup = new NioEventLoopGroup();
try{
	//创建服务器端的启动对象
	ServerBootStrap bootstrap =new ServerBootStrap();
	//使用链式编程来配置参数
	bootstrap.group(bossGroup,workerGroup)//设置两个线程组
			 .channel(NioServerSocketChannel.class) //使用NioServerSocketChannel作为服务器通道的实现
			 .option(ChannelOption.SO_BACKLOG,1024)//初始化服务器的连接队列大小,服务端处理客户端的连接是顺序处理的,所以同一事件只能处理一个客户端连接。多个客户端同时来的时候,服务端将不能处理的连接请求放在队列中等待		
}

我们进入.option方法:

public <T> B option(ChannelOption<T> option, T value) {
        ObjectUtil.checkNotNull(option, "option");
        if (value == null) {
            this.options.remove(option);
        } else {
            this.options.put(option, value);
        }

        return this.self();
    }
//BootStrap中的成员变量中的一个map集合(存储netty程序员启动时需要设置的一些属性)
private final Map<ChannelOption<?>, Object> options = new ConcurrentHashMap();

option方法本质上就是用来设置Netty启动时的一些属性的,例如ChannelOption.SO_BACKLOG就是其中之一,设置的方式就是放到了ServerBootStrap中的一个线程安全集合options中。这些参数的作用后面再分析,然后继续回到业务代码:

//指定EvenLoopGroup中EvenLoop(线程数)的数量
EvenLoopGroup bossGroup = new NioEventLoopGroup(1);
//使用默认数量
EvenLoopGroup bossGroup = new NioEventLoopGroup();
try{
	//创建服务器端的启动对象
	ServerBootStrap bootstrap =new ServerBootStrap();
	//使用链式编程来配置参数
	bootstrap.group(bossGroup,workerGroup)//设置两个线程组
			 .channel(NioServerSocketChannel.class) //使用NioServerSocketChannel作为服务器通道的实现
			 .option(ChannelOption.SO_BACKLOG,1024)//初始化服务器的连接队列大小,服务端处理客户端的连接是顺序处理的,所以同一事件只能处理一个客户端连接。多个客户端同时来的时候,服务端将不能处理的连接请求放在队列中等待		
			 .childHandler((ChannelInitializer)(ch)->{
			 	//对workerGroup的SocketChannel设置处理器
			 	ch.pipeline().addLast(new NettyServerHandler());
			 	});
}

然后就是调用 .childHandler设置channel的处理器链了,这也是最核心的部分了。我们进入该方法:


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

上面代码就是将ChannelHandler对象赋值给ServerBootStrap this.childHandler属性。

看到这里我们不难发现整个链式调用的过程其实都是在给启动器ServerBootStrap对象的成员变量进行赋值。

到此ServerBootStrap对象就创建完成了,我们继续进行业务代码:

//指定EvenLoopGroup中EvenLoop(线程数)的数量
EvenLoopGroup bossGroup = new NioEventLoopGroup(1);
//使用默认数量
EvenLoopGroup bossGroup = new NioEventLoopGroup();
try{
	//创建服务器端的启动对象
	ServerBootStrap bootstrap =new ServerBootStrap();
	//使用链式编程来配置参数
	bootstrap.group(bossGroup,workerGroup)//设置两个线程组
			 .channel(NioServerSocketChannel.class) //使用NioServerSocketChannel作为服务器通道的实现
			 .option(ChannelOption.SO_BACKLOG,1024)//初始化服务器的连接队列大小,服务端处理客户端的连接是顺序处理的,所以同一事件只能处理一个客户端连接。多个客户端同时来的时候,服务端将不能处理的连接请求放在队列中等待		
			 .childHandler((ChannelInitializer)(ch)->{
			 	//对workerGroup的SocketChannel设置处理器
			 	ch.pipeline().addLast(new NettyServerHandler());
			 	});
	//绑定一个端口并且同步,生成了一个ChannelFuture异步对象,通过isDone()等方法可以判断异步事件的执行情况
	//启动服务器并绑定端口,bind是异步操作,sync方法是等待异步操作执行完毕(阻塞的)
	ChannelFuture cf=bootstrap.bind(9000).sync();
}

上面的代码是非常重要的一句代码,这里我们详细分析一下,我们进入bind方法:

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

上面代码首先将我们传入的端口号9000封装为一个InetSocketAddress对象,然后调用ServerBootStrapbind方法,我们进入该bind方法:

public ChannelFuture bind(SocketAddress localAddress) {
        this.validate();
        return this.doBind((SocketAddress)ObjectUtil.checkNotNull(localAddress, "localAddress"));
    }

核心方法是this.doBind方法,然后参数就是我们前面传入的端口号,继续进入this.doBind方法:

private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = this.initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        } else if (regFuture.isDone()) {
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() {
                public void operationComplete(ChannelFuture future) throws Exception {
                    Throwable cause = future.cause();
                    if (cause != null) {
                        promise.setFailure(cause);
                    } else {
                        promise.registered();
                        AbstractBootstrap.doBind0(regFuture, channel, localAddress, promise);
                    }

                }
            });
            return promise;
        }
    }

我们重点分析上面代码,首先第一句核心代码是 final ChannelFuture regFuture = this.initAndRegister();,它调用了this.initAndRegister方法获取了一个ChannelFuture对象,我们看看initAndRegister方法:

final ChannelFuture initAndRegister() {
        Channel channel = null;

        try {
            channel = this.channelFactory.newChannel();
            this.init(channel);
        } catch (Throwable var3) {
            if (channel != null) {
                channel.unsafe().closeForcibly();
                return (new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE)).setFailure(var3);
            }

            return (new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE)).setFailure(var3);
        }

        ChannelFuture regFuture = this.config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }

channel = this.channelFactory.newChannel();,前面在分析ServerBootStrap对象构建过程中,分析了.channel(NioServerSocketChannel.class)底层就保存了一个channel工厂对象,这里就将前面创建的工厂对象拿出来创建了一个channel对象的实例,我们进入newChannel()方法:

public T newChannel() {
        try {
            return (Channel)this.constructor.newInstance();
        } catch (Throwable var2) {
            throw new ChannelException("Unable to create Channel from class " + this.constructor.getDeclaringClass(), var2);
        }
    }

channel工厂对象内部保存的其实就是我们传入的NioServerSocketChannel.class类型的构造函数,这里就用保存的构造函数使用反射创建了一个Channel实例。这里调用的是NioServerSocketChannel.class的空构造方法,我们进入该构造方法,看它创建了一些什么东西:

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

然后这里调用了newSocket(DEFAULT_SELECTOR_PROVIDER)方法,我们进入该方法:

 private static java.nio.channels.ServerSocketChannel newSocket(SelectorProvider provider) {
        try {
            return provider.openServerSocketChannel();
        } catch (IOException var2) {
            throw new ChannelException("Failed to open a server socket.", var2);
        }
    }

provider.openServerSocketChannel();这句代码和NIO底层的ServerSocketChannel类的open方法的代码一模一样,这句代码的作用就是,从返回值也可以看出是java.nio.channels.ServerSocketChannel,创建了一个ServerSocketChannel对象。newSocket执行完毕后,看this调用的是哪一个构造函数:

public NioServerSocketChannel(java.nio.channels.ServerSocketChannel channel) {
        super((Channel)null, channel, 16);
        this.config = new NioServerSocketChannelConfig(this, this.javaChannel().socket());
    }

首先我们进入super方法,参数就是前面newSocket创建的java.nio.channels.ServerSocketChannel对象,以及一个16,这个16就代表NIO中Selectionkey对象的OP_ACCEPT事件public static final int OP_ACCEPT=1<<4;

 protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        //记录serverSocketChannel
        this.ch = ch;
        //记录serverSocketChannel感兴趣的事件
        this.readInterestOp = readInterestOp;

        try {
       		//设置channel为非阻塞模式
            ch.configureBlocking(false);
        } catch (IOException var7) {
            try {
                ch.close();
            } catch (IOException var6) {
                logger.warn("Failed to close a partially initialized socket.", var6);
            }

            throw new ChannelException("Failed to enter non-blocking mode.", var7);
        }
    }

我们继续进入super方法:

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

上面代码核心就是创建了this.pipeline对象,我们进入newChannelPipeline方法,看看底层是如何创建Pipeline的:

 protected DefaultChannelPipeline newChannelPipeline() {
        return new DefaultChannelPipeline(this);
    }


    protected DefaultChannelPipeline(Channel channel) {
        this.channel = (Channel)ObjectUtil.checkNotNull(channel, "channel");
        this.succeededFuture = new SucceededChannelFuture(channel, (EventExecutor)null);
        this.voidPromise = new VoidChannelPromise(channel, true);
        //默认放了一个尾节点
        this.tail = new TailContext(this);
        //默认放了一个头节点
        this.head = new HeadContext(this);
        //说明是一个双向链表
        this.head.next = this.tail;
        this.tail.prev = this.head;
    }

从上面的代码我们就可以看出了ChannelPipeline的底层结构了:
在这里插入图片描述
到此channelFactory.newChannel底层的逻辑我们就看完了,其实就是创建了一个ServerSocketChannel,然后构建了pipeline对象,我们继续回到initAndRegister()方法:

final ChannelFuture initAndRegister() {
        Channel channel = null;

        try {
            channel = this.channelFactory.newChannel();
            this.init(channel);
        } catch (Throwable var3) {
            if (channel != null) {
                channel.unsafe().closeForcibly();
                return (new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE)).setFailure(var3);
            }

            return (new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE)).setFailure(var3);
        }

        ChannelFuture regFuture = this.config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }

然后就行调用 this.init(channel);方法,我们进入该方法:

//参数就是前面创建的NioServerSocketChannel对象
void init(Channel channel) {
        setChannelOptions(channel, (Map.Entry[])this.options0().entrySet().toArray(newOptionArray(0)), logger);
        setAttributes(channel, (Map.Entry[])this.attrs0().entrySet().toArray(newAttrArray(0)));
        ChannelPipeline p = channel.pipeline();
        final EventLoopGroup currentChildGroup = this.childGroup;
        final ChannelHandler currentChildHandler = this.childHandler;
        final Map.Entry<ChannelOption<?>, Object>[] currentChildOptions = (Map.Entry[])this.childOptions.entrySet().toArray(newOptionArray(0));
        final Map.Entry<AttributeKey<?>, Object>[] currentChildAttrs = (Map.Entry[])this.childAttrs.entrySet().toArray(newAttrArray(0));
        p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {
            public void initChannel(final Channel ch) {
            //拿到前面创建的管道
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = ServerBootstrap.this.config.handler();
                if (handler != null) {
                    pipeline.addLast(new ChannelHandler[]{handler});
                }

                ch.eventLoop().execute(new Runnable() {
                    public void run() {
                        pipeline.addLast(new ChannelHandler[]{new ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)});
                    }
                });
            }
        }});
    }

上面代码最核心的一部分是下面这一段代码:

 p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = ServerBootstrap.this.config.handler();
                if (handler != null) {
                    pipeline.addLast(new ChannelHandler[]{handler});
                }

                ch.eventLoop().execute(new Runnable() {
                    public void run() {
                        pipeline.addLast(new ChannelHandler[]{new ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)});
                    }
                });
            }
        }});

使用netty的都知道这个就是向管道(ServerSocketChannel)中加入了一个处理器handler,这个handler的作用我们后面再分析,现在pipeline里面有三个handler了。到此init方法执行完毕了,继续回到initAndRegister方法。

final ChannelFuture initAndRegister() {
        Channel channel = null;

        try {
            channel = this.channelFactory.newChannel();
            this.init(channel);
        } catch (Throwable var3) {
            if (channel != null) {
                channel.unsafe().closeForcibly();
                return (new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE)).setFailure(var3);
            }

            return (new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE)).setFailure(var3);
        }

        ChannelFuture regFuture = this.config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }

然后下一句核心代码是ChannelFuture regFuture = this.config().group().register(channel);,签名我们的ServerSocketChannel已经创建完了,但是按照NIO的逻辑,Channel需要注册到Selector中与感兴趣的事件绑定,这句代码就在干这个事,我们进入该方法:

config返回ServerBootStrap对象,group返回EventLoopGroup对象(注意这个EventLoopGroup是bossGroup)

//注意前面group返回的事EventLoopGroup对象,所以这个register的实现就是`MultiThreadEventLoopGroup`
 public ChannelFuture register(Channel channel) {
        return this.next().register(channel);
    }

注意:上面源码本人忘记下载了,看的全是Class文件,但是代码的主体结构还是大差不差,可以对照着看

next方法就是获取下一个EventLoop对象,然后调用EventLoop对象的register方法:

//注意此时调用register方法的是`EventLoop`对象,所以register方法的实现是`SingleThreadEventLoopGroup`
  @Override
    public ChannelFuture register(Channel channel) {
        return register(new DefaultChannelPromise(channel, this));
    }
@Override
    public ChannelFuture register(final ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");
        promise.channel().unsafe().register(this, promise);
        return promise;
    }

@Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            if (eventLoop == null) {
                throw new NullPointerException("eventLoop");
            }
            if (isRegistered()) {
                promise.setFailure(new IllegalStateException("registered to an event loop already"));
                return;
            }
            if (!isCompatible(eventLoop)) {
                promise.setFailure(
                        new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
                return;
            }

            AbstractChannel.this.eventLoop = eventLoop;
			//判断你传入的线程和当前的线程是否是一样
            if (eventLoop.inEventLoop()) {
            	//如果是一样,直接调用register0
                register0(promise);
            } else {
                try {
                	//否则使用你传入的线程来执行任务(这里就实现了异步调用)
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                    logger.warn(
                            "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                            AbstractChannel.this, t);
                    closeForcibly();
                    closeFuture.setClosed();
                    safeSetFailure(promise, t);
                }
            }
        }

上面代码的核心部分是:

//判断你传入的线程和当前的线程是否是一样
            if (eventLoop.inEventLoop()) {
            	//如果是一样,直接调用register0
                register0(promise);
            } else {
                try {
                	//否则使用你传入的线程来执行任务(这里就实现了异步调用)
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                }

netty异步调用逻辑的核心代码就在try语句块中,我们进入execute方法:

@Override
    public void execute(Runnable task) {
        if (task == null) {
            throw new NullPointerException("task");
        }
        boolean inEventLoop = inEventLoop();
        //将我们前面传的Runnable任务添加到了队列中
        addTask(task);
        if (!inEventLoop) {
            startThread();
            if (isShutdown()) {
                boolean reject = false;
                try {
                    if (removeTask(task)) {
                        reject = true;
                    }
                } catch (UnsupportedOperationException e) {
                    // The task queue does not support removal so the best thing we can do is to just move on and
                    // hope we will be able to pick-up the task before its completely terminated.
                    // In worst case we will log on termination.
                }
                if (reject) {
                    reject();
                }
            }
        }

        if (!addTaskWakesUp && wakesUpForTask(task)) {
            wakeup(inEventLoop);
        }
    }

上面代码的核心就是addTask(task);,它将前面传的Runnable任务添加到了队列中(EventLoop的TaskQueue)。

    protected void addTask(Runnable task) {
        if (task == null) {
            throw new NullPointerException("task");
        }
        if (!offerTask(task)) {
            reject(task);
        }
    }

    final boolean offerTask(Runnable task) {
        if (isShutdown()) {
            reject();
        }
        return taskQueue.offer(task);
    }

回到execute方法,将任务丢到队列后,下面就调用 startThread();,启动线程了,我们进入该方法:

    private void startThread() {
        if (state == ST_NOT_STARTED) {
            if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
                boolean success = false;
                try {
                    doStartThread();
                    success = true;
                } finally {
                    if (!success) {
                        STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
                    }
                }
            }
        }
    }


private void doStartThread() {
        assert thread == null;
        //executor就是在创建EventLoopGroup时候创建的那个单线程线程池
        executor.execute(new Runnable() {
            @Override
            public void run() {
                thread = Thread.currentThread();
                if (interrupted) {
                    thread.interrupt();
                }

                boolean success = false;
                updateLastExecutionTime();
                try {
                    SingleThreadEventExecutor.this.run();
                    success = true;
                } catch (Throwable t) {
                    logger.warn("Unexpected exception from an event executor: ", t);
                } finally {
                    for (;;) {
                        int oldState = state;
                        if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
                                SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
                            break;
                        }
                    }

                    // Check if confirmShutdown() was called at the end of the loop.
                    if (success && gracefulShutdownStartTime == 0) {
                        if (logger.isErrorEnabled()) {
                            logger.error("Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
                                    SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must " +
                                    "be called before run() implementation terminates.");
                        }
                    }

                    try {
                        // Run all remaining tasks and shutdown hooks.
                        for (;;) {
                            if (confirmShutdown()) {
                                break;
                            }
                        }
                    } finally {
                        try {
                            cleanup();
                        } finally {
                            // Lets remove all FastThreadLocals for the Thread as we are about to terminate and notify
                            // the future. The user may block on the future and once it unblocks the JVM may terminate
                            // and start unloading classes.
                            // See https://github.com/netty/netty/issues/6596.
                            FastThreadLocal.removeAll();

                            STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
                            threadLock.countDown();
                            if (logger.isWarnEnabled() && !taskQueue.isEmpty()) {
                                logger.warn("An event executor terminated with " +
                                        "non-empty task queue (" + taskQueue.size() + ')');
                            }
                            terminationFuture.setSuccess(null);
                        }
                    }
                }
            }
        });
    }

我们进入核心代码 SingleThreadEventExecutor.this.run();

@Override
    protected void run() {
       //死循环
        for (;;) {
            try {
                try {
                    switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                    case SelectStrategy.CONTINUE:
                        continue;

                    case SelectStrategy.BUSY_WAIT:
                        // fall-through to SELECT since the busy-wait is not supported with NIO

                    case SelectStrategy.SELECT:
                        select(wakenUp.getAndSet(false));


                        if (wakenUp.get()) {
                            selector.wakeup();
                        }
                        // fall through
                    default:
                    }
                } catch (IOException e) {
                    // If we receive an IOException here its because the Selector is messed up. Let's rebuild
                    // the selector and retry. https://github.com/netty/netty/issues/8566
                    rebuildSelector0();
                    handleLoopException(e);
                    continue;
                }

                cancelledKeys = 0;
                needsToSelectAgain = false;
                final int ioRatio = this.ioRatio;
                if (ioRatio == 100) {
                    try {
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        runAllTasks();
                    }
                } else {
                    final long ioStartTime = System.nanoTime();
                    try {
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        final long ioTime = System.nanoTime() - ioStartTime;
                        runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
            // Always handle shutdown even if the loop processing threw an exception.
            try {
                if (isShuttingDown()) {
                    closeAll();
                    if (confirmShutdown()) {
                        return;
                    }
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
        }
    }

上面代码的核心就是 select(wakenUp.getAndSet(false));方法,我们进入该方法:

private void select(boolean oldWakenUp) throws IOException {
		//获得了selector
        Selector selector = this.selector;
        try {
            int selectCnt = 0;
            long currentTimeNanos = System.nanoTime();
            long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);

            long normalizedDeadlineNanos = selectDeadLineNanos - initialNanoTime();
            if (nextWakeupTime != normalizedDeadlineNanos) {
                nextWakeupTime = normalizedDeadlineNanos;
            }
            for (;;) {
                long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
                if (timeoutMillis <= 0) {
                    if (selectCnt == 0) {
                        selector.selectNow();
                        selectCnt = 1;
                    }
                    break;
                }

                // If a task was submitted when wakenUp value was true, the task didn't get a chance to call
                // Selector#wakeup. So we need to check task queue again before executing select operation.
                // If we don't, the task might be pended until select operation was timed out.
                // It might be pended until idle timeout if IdleStateHandler existed in pipeline.
                if (hasTasks() && wakenUp.compareAndSet(false, true)) {
                    selector.selectNow();
                    selectCnt = 1;
                    break;
                }

                int selectedKeys = selector.select(timeoutMillis);
                selectCnt ++;

                if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
                    // - Selected something,
                    // - waken up by user, or
                    // - the task queue has a pending task.
                    // - a scheduled task is ready for processing
                    break;
                }
                if (Thread.interrupted()) {
                    // Thread was interrupted so reset selected keys and break so we not run into a busy loop.
                    // As this is most likely a bug in the handler of the user or it's client library we will
                    // also log it.
                    //
                    // See https://github.com/netty/netty/issues/2426
                    if (logger.isDebugEnabled()) {
                        logger.debug("Selector.select() returned prematurely because " +
                                "Thread.currentThread().interrupt() was called. Use " +
                                "NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
                    }
                    selectCnt = 1;
                    break;
                }

                long time = System.nanoTime();
                if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
                    // timeoutMillis elapsed without anything selected.
                    selectCnt = 1;
                } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
                        selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
                    // The code exists in an extra method to ensure the method is not too big to inline as this
                    // branch is not very likely to get hit very frequently.
                    selector = selectRebuildSelector(selectCnt);
                    selectCnt = 1;
                    break;
                }

                currentTimeNanos = time;
            }

            if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                            selectCnt - 1, selector);
                }
            }
        } catch (CancelledKeyException e) {
            if (logger.isDebugEnabled()) {
                logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
                        selector, e);
            }
            // Harmless exception - log anyway
        }
    }

上面代码的核心代码就是int selectedKeys = selector.select(timeoutMillis);,这里就调用了Selector的select方法,学习NIO后我们知道这个方法是一个阻塞方法,方法结果的返回值就是已经就绪的事件。这里设置了超时时间,到了超时时间不管有没有事件就绪,select方法就会执行完毕。上面代码核心就是调用了一个select方法,回到run方法:

if (ioRatio == 100) {
                    try {
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        runAllTasks();
                    }
                }

回到线程模型图,我们就回到了下面红圈部分:

在这里插入图片描述

第二步就是调用processSelectedKeys()方法,我们进入该方法:

    private void processSelectedKeys() {
        if (selectedKeys != null) {
            processSelectedKeysOptimized();
        } else {
            processSelectedKeysPlain(selector.selectedKeys());
        }
    }

如果selectedKeys不为空,进入processSelectedKeysOptimized方法:

private void processSelectedKeysOptimized() {
        for (int i = 0; i < selectedKeys.size; ++i) {
            final SelectionKey k = selectedKeys.keys[i];
            // null out entry in the array to allow to have it GC'ed once the Channel close
            // See https://github.com/netty/netty/issues/2363
            selectedKeys.keys[i] = null;

            final Object a = k.attachment();

            if (a instanceof AbstractNioChannel) {
                processSelectedKey(k, (AbstractNioChannel) a);
            } else {
                @SuppressWarnings("unchecked")
                NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                processSelectedKey(k, task);
            }

            if (needsToSelectAgain) {
                // null out entries in the array to allow to have it GC'ed once the Channel close
                // See https://github.com/netty/netty/issues/2363
                selectedKeys.reset(i + 1);

                selectAgain();
                i = -1;
            }
        }
    }

上面核心代码就是NIO代码的逻辑,这里就不详细分析了。回到run方法,下面就会调用runAllTasks()方法:

protected boolean runAllTasks() {
        assert inEventLoop();
        boolean fetchedAll;
        boolean ranAtLeastOne = false;

        do {
            fetchedAll = fetchFromScheduledTaskQueue();
            if (runAllTasksFrom(taskQueue)) {
                ranAtLeastOne = true;
            }
        } while (!fetchedAll); // keep on processing until we fetched all scheduled tasks.

        if (ranAtLeastOne) {
            lastExecutionTime = ScheduledFutureTask.nanoTime();
        }
        afterRunningAllTasks();
        return ranAtLeastOne;
    }

上面代码就是将EventLoop的TaskQueue中的任务拿出来执行。这里我们就得回到前面的register方法,因为在这里我们加入的一个任务会在runAllTasks方法中执行:

  eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });

我们进入register0方法:

private void register0(ChannelPromise promise) {
            try {
                // check if the channel is still open as it could be closed in the mean time when the register
                // call was outside of the eventLoop
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;
                doRegister();
                neverRegistered = false;
                registered = true;

                // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
                // user may already fire events through the pipeline in the ChannelFutureListener.
                pipeline.invokeHandlerAddedIfNeeded();

                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();
                // Only fire a channelActive if the channel has never been registered. This prevents firing
                // multiple channel actives if the channel is deregistered and re-registered.
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                // Close the channel directly to avoid FD leak.
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }

注意上面代码第一个十分核心的代码 doRegister();,我们进入该方法:

@Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    // Force the Selector to select now as the "canceled" SelectionKey may still be
                    // cached and not removed because no Select.select(..) operation was called yet.
                    eventLoop().selectNow();
                    selected = true;
                } else {
                    // We forced a select operation on the selector before but the SelectionKey is still cached
                    // for whatever reason. JDK bug ?
                    throw e;
                }
            }
        }
    }

javaChannel()就是前面创建的ServerSocketChannel,然后调用register方法将ServerSocketChannel注册到了Selector中了,然后绑定了感兴趣的事件。这里也是NIO的逻辑。回到register0方法,下一个核心代码是:

 pipeline.invokeHandlerAddedIfNeeded();
                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();
                // Only fire a channelActive if the channel has never been registered. This prevents firing
                // multiple channel actives if the channel is deregistered and re-registered.
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        beginRead();
                    }
                }

invokeHandlerAddedIfNeeded();这里底层源码比较复杂,它底层其实会调用我们之前在初始化pipeLine的时候加入的一个handler:

p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = ServerBootstrap.this.config.handler();
                if (handler != null) {
                    pipeline.addLast(new ChannelHandler[]{handler});
                }

                ch.eventLoop().execute(new Runnable() {
                    public void run() {
                        pipeline.addLast(new ChannelHandler[]{new ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)});
                    }
                });
            }
        }});

执行了上面的代码PipeLine里面的handler的情况变化如下:
在这里插入图片描述
继续执行下面代码:

pipeline.fireChannelRegistered();
	
	//head就是前面双向链表的头节点
  @Override
    public final ChannelPipeline fireChannelRegistered() {
        AbstractChannelHandlerContext.invokeChannelRegistered(head);
        return this;
    }

 static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
        //同步方式调用
            next.invokeChannelRegistered();
        } else {
        //异步方式调用
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelRegistered();
                }
            });
        }
    }

private void invokeChannelRegistered() {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelRegistered(this);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelRegistered();
        }
    }

//AbstractRemoteAddressFilter
  @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        handleNewChannel(ctx);
       ctx.fireChannelRegistered();
    }

((ChannelInboundHandler) handler()).channelRegistered(this);就调用了handler的channelRegistered方法,然后ctx.fireChannelRegistered();就是调用pipeline中下一个handler的核心逻辑。

上面就介绍了ServerBootStrap启动的核心逻辑。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

React + 项目(从基础到实战) -- 第11期

目标 问卷编辑器的开发 设计UI - 拆分布局 水平垂直居中 画布 y方向滚动 自定义问卷组件 后端 返回组件数据 //获取单个问卷信息{url: /api/question/:id,method: get,response: () > {return {errno: 0,data: {id: Random.id(),title: Random.ctitle(),componentList:[//…

1W 3KVDC 隔离双输出 DC/DC 电源模块 ——TPD 系列

TPD系列提供双独立输出电压&#xff0c;并且两组电压可以不同&#xff0c;这样就节省一个电源模块&#xff0c;特别适合一块板上有多个不同电压要求的设计&#xff0c;而外形尺寸和TPA一样&#xff0c;工作温度范围广-40℃到 105℃。

【go项目01_学习记录05】

学习记录 1 依赖管理 Go Modules1.1 弃用 $GOPATH1.2 Go Modules 日常使用1.2.1 初始化生成go.mod文件1.2.2 Go Proxy代理1.2.3 go.mod文件查看1.2.4 go.sum文件查看1.2.5 indirect 含义1.2.6 go mod tidy 命令1.2.7 清空 Go Modules 缓存1.2.8 下载依赖1.2.9 所有 Go Modules …

sip转webrtc方案

技术选型 由于很多企业会议协议用的主要是webrtc&#xff0c;但是项目上很多时候的一些旧设备只支持sip协议&#xff0c;并不支持webrtc协议。所以sip和webrtc的相互转换就很有必要。 流媒体服务mediasoup本身并不支持sip协议。那么如何实现sip转webrtc呢&#xff1f; 根据调研…

攻防世界-xff-referer

题目信息 分析过程 显示ip必须为123.123.123.123&#xff0c;则进行伪造 解题过程 打开repeator 提示必须来自https://www.google.com&#xff0c;则再次构造Referer 相关知识 x-forwarded-for 和 referer的区别: x-forwarded-for 用来证明ip的像是“127.0.0.1”这种&a…

迭代器解释(C++)

一、什么是迭代器 为了提高C编程的效率&#xff0c;STL&#xff08;Standard Template Library&#xff09;中提供了许多容器&#xff0c;包括vector、list、map、set等。然而有些容器&#xff08;vector&#xff09;可以通过下标索引的方式访问容器里面的数据&#xff0c;但是…

【论文泛读】如何进行动力学重构? 神经网络自动编码器结合SINDy发现数据背后蕴含的方程

这一篇文章叫做 数据驱动的坐标发现与方程发现算法。 想回答的问题很简单&#xff0c;“如何根据数据写方程”。 想想牛顿的处境&#xff0c;如何根据各种不同物体下落的数据&#xff0c;写出万有引力的数学公式的。这篇文章就是来做这件事的。当然&#xff0c;这篇论文并没有…

流畅的python-学习笔记_对象引用、可变性、垃圾回收

变量不是盒子 即变量是引用&#xff0c;而不是实际内存&#xff0c;多个标识赋值相同变量时&#xff0c;多余标识是引用 标识、相等性、别名 比较对象的值&#xff0c;is比较对象的id。实际调用对象的__eq__方法。is速度比快&#xff0c;因为is不能重载&#xff0c;省去了寻…

TypeScript学习日志-第十九天(namespace命名空间)

namespace命名空间 一、基本用法 namespace 所有的变量以及方法必须要导出才能访问&#xff0c;如图&#xff1a; 二、 嵌套 namespace 可以进行嵌套使用&#xff0c;如图&#xff1a; 它也必须需要导出才能访问 三、合并 当我们出现两个同名的 namespace 它就会合并这两…

4+1视图,注意区分类图与对象图

注意区分类图和对象图。对象图标记的是对象名&#xff0c;命名形式 对象名:类名&#xff0c;或者:类名。这里没有出现冒号&#xff0c;表示的是类图。 对象图(object diagram)。 对象图描述一组对象及它们之间的关系。对象图描述了在类图中所建立的事物实例的静态快照。和类图一…

创造未来知识管理新篇章:Ollama与AnythingLLM联手打造个人与企业的安全知识库!

一 Ollama 1.1 简介 Ollama是一个开源的大型语言模型服务工具,它帮助用户快速在本地运行大模型。通过简单的安装指令,用户可以执行一条命令就在本地运行开源大型语言模型,如Llama 2。Ollama极大地简化了在Docker容器内部署和管理LLM的过程,使得用户能够快速地在本地运行大…

软件测试,软件评测师

如果你想考软件评测师证书&#xff0c;那这篇文章可以帮你少走很多弯路&#xff0c;估计你用别人一半的时间备考就可以通过考试&#xff0c;以下为本人亲身经验哈&#xff0c;你可以先收藏后看哦&#xff0c;提前祝你考试过过过。 如果以后想从事一份软件测试工程师的工作&…

浅析扩散模型与图像生成【应用篇】(二十一)——DALLE·2

21. Hierarchical Text-Conditional Image Generation with CLIP Latents 该文提出一种基于层级式扩散模型的由文本生成图像的方法&#xff0c;也就是大名鼎鼎的DALLE2。在DALLE2之前呢&#xff0c;OpenAI团队已经推出了DALLE和GLIDE两个文生图模型了&#xff0c;其中DALLE是基…

fabric部署调用合约示例

一 打包智能合约 ①进入fabric-samples文件夹下的chaincode/fabcar/go目录下执行 GO111MODULEon go mod vendor下载依赖&#xff08;文件夹下已经有go.mod&#xff0c;不需要使用go mod init生成该module文件&#xff09;②进入到test-network文件下使用以下命令将二进制文件…

2002-2021年各地区平均受教育年限数据(分性别)(含原始数据+计算过程+计算结果)

2002-2021年各地区平均受教育年限数据&#xff08;分性别&#xff09;&#xff08;含原始数据计算过程计算结果&#xff09; 1、时间&#xff1a;2002-2021年 2、来源&#xff1a;国家统计局、统计年鉴、各省年鉴 3、指标&#xff1a;行政区划代码、地区、年份、人均受教育年…

Footprint Analytics 与 Core Chain 达成战略合作

​ 领先的区块链数据解决方案提供商 Footprint Analytics 与比特币驱动、EVM 兼容的 Layer 1 区块链 Core Chain 宣布达成战略合作。此次合作旨在将 Footprint Analytics 的前沿数据解决方案与 Core Chain 的区块链基础设施相结合&#xff0c;共同引领区块链领域的创新发展。 …

苹果挖走大量谷歌人才,建立神秘人工智能实验室;李飞飞创业成立「空间智能」公司丨 RTE 开发者日报 Vol.197

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE&#xff08;Real Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

Apache.commons.lang3 的 isNumber 将会在 lang 4 的时候丢弃

在判断输入的字符串是不是一个数字的时候&#xff0c;我们通常用的最多的方法就是 &#xff1a; NumberUtils.isNumber("12"); 但是这个方法将会在 Lang 4.0 版本中被丢弃。 可以使用的替代方法为&#xff1a;isCreatable(String) 通过查看源代码&#xff0c;我们…

【数据结构】有关环形链表题目的总结

文章目录 引入 - 快慢指针思考 - 快慢指针行走步数进阶 - 寻找环形链表的头 引入 - 快慢指针 141-环形链表 - Leetcode 关于这道题&#xff0c;大家可以利用快慢指针&#xff0c;一个每次走两步&#xff0c;一个每次走一步&#xff0c;只要他们有一次相撞了就代表说这是一个链…

Leetcode编程练习

面试题-消失的数字 . - 力扣&#xff08;LeetCode&#xff09; class Solution { public:void reverse(vector<int>& nums, int start, int end) {while (start < end) {swap(nums[start], nums[end]);start 1;end - 1;}}void rotate(vector<int>& …
最新文章