当前位置: 首页 > news >正文

Netty源码系列 之 EventLoop run()方法 源码

EventLoop[实现类为NioEventLoop,我们研究NioEventLoop即可]

EventLoop是一个单线程的线程池

核心作用:处理执行IO操作(accept,read,write事件),普通任务,定时任务

EventLoop封装了Selector复用器,Thread线程,以及任务队列

为什么EventLoop需要一个任务队列?

因为EventLoop是一个单线程的线程池,如果有多个任务请求过来时,可能同时处理不了这么多,所以需要暂时存储到一个任务队列中逐个慢慢处理。

我们上面说了EventLoop封装了Selector复用器,Thread线程,以及任务队列,如何证明?

先看EventLoop体系图,它肯定继承了各种类,各种父类的属性为EventLoop提供了以上属性 或者是 EventLoop自己这个类也会封装一些属性。

当然,由于EventLoop只是一个接口,所以当我们需要研究观察NioEventLoop的体系图:

如下分析:

1.

2.

NioEventLoop继承ScheduledExecutorService接口,所以该接口实现类就是NioEventLoop的父类

SingleThreadEventExecutor这个父类提供任务队列和线程这两个属性

3.AbstractScheduledEventExecutor类是NioEventLoop的父类,所以NioEventLoop继承拥有父类的所有属性

AbstractScheduledEventExecutor类为NioEventLoop提供用于存储定时任务的优先级队列这个属性

总结:

NioEventLoop实现了EventLoop接口,NioEventLoop是一个集大成者,它不仅仅是一个普通的线程对象,而是一个具有Selector,任务队列缓存功能等的优秀组件。

由此也可以看出,一个EventLoop[NioEventLoop]对应一个Selector复用器类

所以:EventLoop接口实现类NioEventLoop拥有以下核心属性:

1.private Selector selector; //包装后的Selector对象

2.private Selector unwrappedSelector;//未包装的Selector对象

3.private final Queue<Runnable> taskQueue;//负责存储未执行完的普通任务。因为NioEventLoop是单线程,如果有多个任务,那么任务队列是必须的。

4.private volatile Thread thread;//线程对象,真正执行代码逻辑的对象

5. PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue;//用于存储定时任务的优先级队列。该队列具体底层的实现对应的数据结构HashWheelTimer

这么多属性共同铸就了NioEventLoop的功能特性

  • 问题:NioEventLoop中的Selector选择器什么时候被创建的?

在NioEventLoop的构造器中:

1.final SelectorTuple selectorTuple = openSelector();

该方法就是先获取windows操作系统(或其他操作系统)所对应的最原生的Selector对象,也就是未包装的Selector对象:unwrappedSelector。然后再通过反射技术把unwrappedSelector对应的属性等赋值给Selector,返回SelectorTuple对象,该对象包含Selector对象和unwrappedSelector对象

2.this.selector = selectorTuple.selector;

获取到包装后的Selector对象赋值给属性selector

3.this.unwrappedSelector = selectorTuple.unwrappedSelector;

获取到未包装的Selector对象给属性unwrappedSelector

进入openSelector方法:

openSelector方法:

1.把unwrappedSelector对应的属性赋值给selector

2.会把通过unwappedSelector拿到 selectedKey对应的HashSet集合,然后把该集合中的数据赋值给selector的SelectionKey[] keys;数组里。Netty底层反射完成。

补充:

Selector属性和unwrappedSelector属性二者所对应的对象有什么区别?

在Netty中,这两个属性的区别如下:

  1. private Selector selector: 这是Netty中的一个属性,表示经过包装的Selector对象。Netty在创建和管理Selector时,会对其进行包装,以提供更高级的功能和对底层Selector的优化。这个selector属性是Netty的EventLoop中使用的,用于处理网络事件。通过selector,Netty可以监听和处理多个Channel上的事件,并通过事件驱动的方式进行网络编程。
  2. private Selector unwrappedSelector: 这也是Netty中的一个属性,表示未经包装的原始底层Selector对象。这个unwrappedSelector属性是Netty的EventLoop中使用的,用于处理底层的I/O事件。Netty会使用自己的EventLoopGroup来创建和管理Selector,并将其包装成unwrappedSelector。通过unwrappedSelector,Netty可以对底层Selector进行更高级的操作和管理,例如处理空轮询、优化事件的触发和取消等。

总结起来,selector是经过Netty包装的Selector对象,用于处理网络事件,而unwrappedSelector是未经包装的底层Selector对象,用于处理底层的I/O事件。它们在Netty的EventLoop中扮演不同的角色,分别负责处理不同层次的事件。

Netty的Selector对底层的unwrappedSelector进行了封装,以提供更高级的操作和管理。其中包括处理空轮询的情况,优化事件的触发和取消等。

通过封装unwrappedSelector,Netty可以在底层unwrappedSelector的基础上实现一些额外的功能和优化,以提高网络编程的性能和可靠性,最终得出了NioEventLoop中的selector属性。这些功能和优化包括但不限于:

  1. 处理空轮询:当底层Selector在轮询时没有任何事件发生时,Netty会进行特殊处理,避免空轮询的问题,从而提高了事件的处理效率。【具体如何处理空轮询的?后续会分析到,使用的就是一个计数器计数,等到一定条件后,重构Selector】
  2. 优化事件的触发和取消:Netty可以根据具体的业务需求和网络情况,对事件的触发和取消进行优化。例如,可以通过调整事件的触发条件和取消条件,避免不必要的事件触发和处理,提高网络应用的性能和响应速度。
  • 自己写一个反射案例吧
@Slf4j
public class User {private String name;
}
package com.messi.netty_source_03.Test02;import lombok.extern.slf4j.Slf4j;import java.lang.reflect.Field;@Slf4j
public class TestUser {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {User user = new User();Class<? extends User> userClazz = user.getClass();Field name = userClazz.getDeclaredField("name");log.debug("name {} ", name);//打破封装  让外部的其他类也可以访问User类的私有属性namename.setAccessible(true);//user.namename.set(user, "leomessi");log.debug("value is {} ",name.get(user));}
}

测试:

  • NioEventLoop中的两个属性:selector属性比unwrappedSelector属性,哪一个使用的多?

selector属性比unwrappedSelector属性用的多

这两者有啥区别:

1.unwrappedSelector是原生的操作系统分配的Selector对象,具有很多Bug,也是java原生NIO所使用的Selector复用器。所以NIO许多bug,如:jdk1.7之前的空轮询bug,莫名其妙的bug。

2.

通过封装unwrappedSelector,Netty可以在底层unwrappedSelector的基础上实现一些额外的功能和优化,以提高网络编程的性能和可靠性,最终得出了NioEventLoop中的selector属性。这些功能和优化包括但不限于:

  1. 处理空轮询:当底层Selector在轮询时没有任何事件发生时,Netty会进行特殊处理,避免空轮询的问题,从而提高了事件的处理效率。【具体如何处理空轮询的?后续会分析到,使用的就是一个计数器计数,等到一定条件后,重构Selector】
  2. 优化事件的触发和取消:Netty可以根据具体的业务需求和网络情况,对事件的触发和取消进行优化。例如,可以通过调整事件的触发条件和取消条件,避免不必要的事件触发和处理,提高网络应用的性能和响应速度。

到目前为止:我们可以发现,Selector做了什么优化?存储Selector上对应的SelectionKey时,原生JavaNIO使用的是Set集合,而Netty使用的是数组。啥是SelectionKey?在上一个小节的最后总结过。

演示如下:

NIO:

Netty:

总结:

这仅仅是Netty对于Selector的优化。后续还有FastThreadLocal,HashWheelTimer的优化。

Netty让SelectedKey存储形式从javaNIO中的Set集合类型,转变成数组类型。数组类型更加有利于提升数据的遍历查找的性能。但是你不可以说仅仅这一个优化,就让Netty成为了高性能框架。你也不能说在高并发场景下,使用数组的绝对优势。还是那句话,这一个优化只是Netty对javaNIO或java jdk原生提供的类型的优化之一,后续还有FastThreadLocal,HashWheelTimer,这两种数据结构也是Netty对jdk中原生数据结构的优化的代表作。正是因为这么多个优化的共同协作下,才让Netty成为了一个异步事件监听回调的高性能框架。

并且你不能说jdk原生的数据结构不好,而是在Netty通信所在的高并发场景下,我们需要选取适应场景所需的数据结构,如果原生数据结构不符合,那么需要做优化定制。

EventLoop[NioEventLoop]的Nio线程什么时候启动呢?&& 如何进行IO操作,普通任务处理,定时任务处理呢?&& Netty如何解决NIO-Selector空转的问题

  • 测试用例
package com.messi.netty_source_03.Test03;import io.netty.channel.EventLoop;
import io.netty.channel.nio.NioEventLoopGroup;import java.util.concurrent.TimeUnit;public class TestEventloop2 {public static void main(String[] args) {NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();//设置IoRatio//eventLoopGroup.setIoRatio(80);EventLoop eventLoop = eventLoopGroup.next();//一切准备好后,直接开始执行该任务eventLoop.execute(() -> {System.out.println("hello suns");});//定时任务。一切准备好后,也得再过200秒后才会执行该定时任务eventLoop.schedule(() -> {System.out.println("TestEventloop2.main");}, 200, TimeUnit.SECONDS);}
}

debug源码过程:

1.

2.

3.

4.

5.


 

6.进入doStartThread方法

核心:run方法【涉及IO,普通任务,定时任务时间的分配 && Netty如何解决NIO-Selector空转的问题】

接着上面进行debug,进入run方法:

1.

我们所做的逻辑就是:不要让selector.select()一直阻塞,因为我们还需要处理定时任务和普通任务。

2.

三种情况:

情况1:

IO操作全部做完后再执行普通任务。但是当普通任务执行的过程中,又有新的IO操作来了,如果任务还没有执行完,那么还是会执行未处理完的普通任务【为什么会这样?因为ranTasks=runAllTasks()没有传递参数,所以执行任务操作时,没有限制时间,会一直执行到任务处理结束】。显然,这种ioRatio==100的做法是不正确的。所以说,要设计一个比例,让普通任务不能完全阻塞影响到后续进入的IO网络通信的执行【因为网络通信是不能延迟的!就算普通任务没有执行完,网络通信IO也要先执行,所以说设置一个好的ioRatio比例值是非常重要的】

情况2:

ioRatio!=100,先执行完全部的IO操作。然后执行任务时会根据ioRatio计算出一个执行时间长度限制,即使任务到了时间限制后没有执行完毕,那么也不能再执行任务了,而是会跳转去执行IO操作,IO操作可以无限制时间的去执行,直到IO执行完毕【因为IO操作是网络通信,用户不能等】

这样就解决了情况1的问题【任务无截止时间的执行导致后续进来的IO执行的阻塞】

情况3:

此时只有任务需要执行,没有IO操作。会没有限制时间的执行所有的任务。

如何解决epoll空转问题?

在linux操作系统下的epoll模型,如果我们把java代码部署在linux操作系统上并且会调用epoll的话。

在原生jdk-NIO,从jdk1.6开始出现selector.select()空转[epoll空转]问题,号称在jdk1.7已经修复了,但是jdk1.8还是会存在空转的问题。只不过空转的几率会越来越小。修复了很多版本。但是记住:空转这个问题是一个极其偶发的事情,几率很低。

先说一下,什么叫做epoll空转?

所谓空转就是:当前没有IO事件或任务[普通任务或定时任务]时,本应该阻塞的selector.select()方法突然停止阻塞然后不断的执行while循环,所谓空轮询【空转】就是:没有意义的不断循环,因为此时压根就没有任务或IO事件需要你处理。一旦空轮询发生,也就是一直会while死循环,那么cpu占有率达到100%是迟早的事情

但是Netty解决了这个臭名昭著的问题。

如何解决的呢?就是在run方法中定义了一个计数器变量,当没有IO或任务执行时,但是该计数器变量在一定时间内增加到512后会执行重构selector的操作。重构后的selector可能就不会出现空转问题了,因为空转是一个极其偶发的事情,几率很低。但是有一点你需要知道:Netty并没有从本质上解决jdk原生NIO调用epoll时的空转问题,而是当出现空转时,我们重构selector,毕竟空转就是一个偶发几率低的事件,那么重构后很大可能就不会空转了。

直接看源码中如何解决的:

1.

2.

3.

Selector重构涉及到很多方面,不再过多描述。

EventLoop如何处理IO操作

NioEventLoop找到selector对象,selector找到对应所有的SelectionKey,SelectionKey再找到对应的attachment附件:Channel对象。拿到Channel对象可以获取到pipeline对象,进行网络IO操作。

EventLoop--->NioEventLoop--->selector--->SelectionKey[]

--->NioServerSocketChannel/NioSocketChannel[附件]--->pipeline--->网络IO操作

  • debug代码如下
package com.messi.netty_source_03.class_04;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;public class NettyServer {public static void main(String[] args) throws InterruptedException {EventLoopGroup eventLoopGroup = new NioEventLoopGroup();ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.group(eventLoopGroup);serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new LoggingHandler());}});Channel channel = serverBootstrap.bind(8000).sync().channel();channel.closeFuture().sync();}}
package com.messi.netty_source_03.class_04;import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.net.InetSocketAddress;
import java.nio.charset.Charset;public class MyNettyClient {private static final Logger log = LoggerFactory.getLogger(MyNettyClient.class);public static void main(String[] args) throws InterruptedException {log.debug("myNettyClientStarter------");EventLoopGroup eventLoopGroup = new NioEventLoopGroup();Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);Bootstrap group = bootstrap.group(eventLoopGroup);//32 ---> 1 IO操作 31线程bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new LoggingHandler());ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {if (ctx.channel().isWritable()) {ByteBufAllocator alloc = ctx.alloc();ByteBuf buffer = alloc.buffer();buffer.writeCharSequence("xiaohei", Charset.defaultCharset());ctx.writeAndFlush(buffer);}}});}});Channel channel = bootstrap.connect(new InetSocketAddress(8000)).sync().channel();channel.closeFuture().sync();}
}
  • debug流程【主要记录IO操作的流程】

预备工作:

debug方式启动服务端

在服务端debug调试完成bind绑定操作后,【bind绑定的流程省略】。

bind流程:完成NioServerSocketChannel的注册,使得服务端完成初始化操作。给NioServerSocketChannel分配一个对应的SelectionKey。并且完成事件的注册。

服务端完成bind操作后,使用运行方式启动客户端

直接快进到IO操作的处理

1.selector.select()方法停止阻塞,向下找到IO事件处理的方法

2.

3.

如果对存储SelectionKey的结构有优化,那么使用数组存储SelectionKey,则走第一个if分支

如果对存储SelectionKey的结构无优化,还是使用Set集合存储SelectionKey,则走第二个else分支

知识点回顾:

附件是什么?

附件就是NioChannel,可以是NioServerSocketChannel,也可以是NioSocketChannel。通过这些Channel才可以拿到对应的pipeline管道,通过管道我们才可以进行相应的读写IO,连接的网络操作。正是通过这种附件的方式,NIO才和Netty完美的整合在了一起。

4.

5.

6.

7.

8.

9.

10.

11.

12.

13.

14.

15.

16.

17.

18.

为什么在读数据的时候进行循环读取?

原因:我们知道从网卡接收的数据一开始是拷贝到recv-socket接收缓冲区的,我们在应用层进行开启ByteBuf进行读入socket缓冲区的数据,如果socket缓冲区的数据大于ByteBuf的大小,那么一次性是读取不完socket缓冲区的数据的。所以要循环读取。

所以fireChannelRead(byteBuf)方法被多次调用并不就是等同于Client客户端发了多次数据:

可能就像上面分析的那样,客户端只发了一次数据,服务端recv-socket缓冲区接收这份数据,但是由于服务端应用层的ByteBuf缓冲区过小,导致服务端需要处理读入多次才能处理完毕,导致channelRead方法被调用多次。

当然,也有可能是客户端发了多次数据,服务端channelRead方法被调用多次进行接收。

19.

doReadBytes方法

20.

四个条件,任意一个条件为false,那么就返回false,如果返回false,那么循环就会退出。

下面来说一下这四个条件的情况:

条件1:一般都为true

条件2:如果ByteBuf写满了,那么说明可能socket缓冲区数据还没读完,那么继续读,为true。如果ByteBuf没写满,说明socket缓冲区数据读完了,无需再继续读,为false。

条件3:do while循环最多执行16次,如果达到16次,循环退出即可。如果16次没有完成处理,那么也会退出,但是selector.select()依然会继续监听read方法,可以下一次再继续read。

为什么要设置一个边界值16?

为什么要循环16次后如果还没有处理完,那么就退出?

因为对于IO操作来说,它是一个强阻塞且数据量较大的操作。IO操作本身占用线程资源时间就长,很有可能16次处理不完,但是对于其他的操作而言,把线程资源分配给它们,很有可能CPU一瞬间就执行完毕了这些非IO任务。所以设置了一个边界值16,防止其他非IO的task一直处于饥饿等待。

其实这就是多路复用线程资源的思想,让一个线程资源给多个客户端的多个任务去使用。并且切记这里不是阻塞执行IO操作,只是打断,给其他任务使用执行一下啦。

但是一般循环16次都可以处理完socket缓冲区的数据。一般16次循环,并且在动态调整ByteBuf的情况下,可以读取处理16GB的socket缓冲区数据。大吧。

条件4:一般都为true

相关文章:

Netty源码系列 之 EventLoop run()方法 源码

EventLoop[实现类为NioEventLoop&#xff0c;我们研究NioEventLoop即可] EventLoop是一个单线程的线程池 核心作用&#xff1a;处理执行IO操作&#xff08;accept&#xff0c;read&#xff0c;write事件&#xff09;&#xff0c;普通任务&#xff0c;定时任务 EventLoop封装…...

ChatGPT 4.0 升级指南, ChatGPT Plus(GPT 4.0) 有何优势?

1.ChatGPT 是什么&#xff1f; ChatGPT 是由 OpenAI 开发的一种基于人工智能的聊天机器人&#xff0c;它基于强大的语言处理模型 GPT&#xff08;Generative Pre-trained Transformer&#xff09;构建。它能够理解人类语言&#xff0c;可以为我们解决实际的问题。 ChatGPT 4.…...

springboot157基于springboot的线上辅导班系统的开发与设计

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **项…...

【机器学习】机器学习简单入门

&#x1f388;个人主页&#xff1a;甜美的江 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;matplotlib &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进…...

考研数据结构笔记(1)

数据结构&#xff08;1&#xff09; 数据结构在学什么&#xff1f;数据结构的基本概念基本概念三要素逻辑结构集合线性结构树形结构图结构 物理结构&#xff08;存储结构&#xff09;顺序存储链式存储索引存储散列存储重点 数据的运算 算法的基本概念什么是算法算法的五个特性有…...

【深度学习理论】持续更新

文章目录 1.统计学习理论 1.统计学习理论 统计学习理论&#xff0c;一款适合零成本搞深度学习的大冤种的方向 从人类学习到机器学习的对比&#xff08;学习的过程分为归纳和演绎 &#xff09;&#xff0c;引出泛化和过拟合的概念。 如何表示归纳的函数规律呢&#xff1f;以监督…...

npm ERR! reason: certificate has expired(淘宝镜像过期)

npm ERR! request to https://registry.npm.taobao.org/yauzl/-/yauzl-2.4.1.tgz failed, reason: certificate has expired 今天在执行npm install命令时&#xff0c;报错百度了下是淘宝证书过期原因 解决方法一 执行下面两个命令再进行npm install即可 npm cache clean --…...

“极简壁纸“爬虫JS逆向·实战

文章目录 声明目标分析确定目标目标检索 代码补全完整代码 爬虫逻辑完整代码 运行结果 声明 本教程只用于交流学习&#xff0c;不可用于商业用途&#xff0c;不可对目标网站进行破坏性请求&#xff0c;请遵守相关法律法规。 目标分析 确定目标 获取图片下载链接 目标检索…...

Django通过Json配置文件分配多个定时任务

def load_config():with open("rule.json", rb)as f:config json.load(f)return configdef job(task_name, config, time_interval):# ... 通过task_name判断进行操作if task_name get_data_times:passdef main():config load_config()for task_name, task_value…...

C++ 搜索二叉树的删除

首先查找元素是否在二叉搜索树中&#xff0c;如果不存在&#xff0c;则返回 要删除的结点可能分下面四种情况&#xff1a; a. 要删除的结点无孩子结点 b. 要删除的结点只有左孩子结点 c. 要删除的结点只有右孩子结点 d. 要删除的结点有左、右孩子结点 看起来有待删除节点有4中…...

构建中国人自己的私人GPT—支持中文

上一篇已经讲解了如何构建自己的私人GPT&#xff0c;这一篇主要讲如何让GPT支持中文。 privateGPT 本地部署目前只支持基于llama.cpp 的 gguf格式模型&#xff0c;GGUF 是 llama.cpp 团队于 2023 年 8 月 21 日推出的一种新格式。它是 GGML 的替代品&#xff0c;llama.cpp 不再…...

elementui 回到顶部报错

<template>Scroll down to see the bottom-right button.<el-backtop target".page-component__scroll .el-scrollbar__wrap"></el-backtop> </template> 使用element的Backtop 回到顶部组件的伙伴们&#xff0c;把官网代码复制到页面使用时…...

go-carbon v2.3.8 发布,轻量级、语义化、对开发者友好的 golang 时间处理库

carbon 是一个轻量级、语义化、对开发者友好的 golang 时间处理库&#xff0c;支持链式调用。 目前已被 awesome-go 收录&#xff0c;如果您觉得不错&#xff0c;请给个 star 吧 github.com/golang-module/carbon gitee.com/golang-module/carbon 安装使用 Golang 版本大于…...

【详解】斗地主随机发牌项目

目录 前言&#xff1a; 1.初始化牌 2.洗牌 3.揭牌 总代码&#xff1a; Card类&#xff1a; CardGame类&#xff1a; Main类&#xff1a; 结语&#xff1a; 前言&#xff1a; 斗地主是全国范围内的一种桌面游戏&#xff0c;本节我们来实现一下斗地主中的简单初始化牌、…...

多账号运营为什么要使用动态住宅代理IP?

对于跨境有多账号运营需求的企业来说&#xff0c;选择正确类型的代理IP对于平稳运行至关重要。但最适合这项工作的代理类型是什么&#xff1f;为了更好地管理不同平台上的多个账户并优化成本&#xff0c;您可以选择动态住宅代理。 一、什么是动态住宅代理 动态住宅代理IP是互联…...

[C++] 如何使用Visual Studio 2022 + QT6创建桌面应用

安装Visual Studio 2022和C环境 [Visual Studio] 基础教程 - Window10下如何安装VS 2022社区版_visual studio 2022 社区版-CSDN博客 安装QT6开源版 下载开源版本QT Try Qt | 开发应用程序和嵌入式系统 | Qt Open Source Development | Open Source License | Qt 下载完成&…...

Arduino 推出基于乐鑫 ESP32-S3 的 STEM 教育机器人

Arduino Alvik 是 Arduino Education 推出的一款新型机器人&#xff0c;可作为一种跨学科工具&#xff0c;为当前教育和未来机器人世界筑起连接的桥梁。Hackster 的 Gareth Halfacree 表示&#xff1a;“Alvik 的设计灵感来自 Arduino 简化复杂技术的理念&#xff0c;同时它也 …...

Blender使用Rigify和Game Rig Tool基础

做动画需要的几个简要步骤&#xff1a; 1.建模 2.绑定骨骼 3.绘制权重 4.动画 1.Rigify是干嘛用的&#xff1f; 》 绑定骨骼 2.Game Rig Tool干嘛用的&#xff1f; 》 修复Rigify绑定骨骼做的动画导入游戏引擎的问题&#xff0c;如果Rigify自身修复了就不需要这个插件了&#…...

【Unity优化(一)】音频优化

整理资教程&#xff1a;https://learn.u3d.cn/tutorial/unity-optimization-metaverse 1.音频优化 音频一般不会成为性能瓶颈&#xff0c;是为了节省内存和优化包体大小。 1.0 文件格式和压缩格式 原始音频资源尽量采用WAV格式。 移动平台音频尽量采用Vorbis压缩格式&#x…...

算法.1-三大排序算法-对数器-二分

三大排序算法&对数器 1.选择排序 Java版 package class01;import java.util.Arrays;public class Code01_SelectionSort {public static void selectionSort(int[] arr) {if (arr null || arr.length < 2) {return;}// 0 ~ N-1 找到最小值&#xff0c;在哪&#xf…...

Objective-C常用命名规范总结

【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名&#xff08;Class Name)2.协议名&#xff08;Protocol Name)3.方法名&#xff08;Method Name)4.属性名&#xff08;Property Name&#xff09;5.局部变量/实例变量&#xff08;Local / Instance Variables&…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论

路径问题的革命性重构&#xff1a;基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中&#xff08;图1&#xff09;&#xff1a; mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制

目录 节点的功能承载层&#xff08;GATT/Adv&#xff09;局限性&#xff1a; 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能&#xff0c;如 Configuration …...

【Post-process】【VBA】ETABS VBA FrameObj.GetNameList and write to EXCEL

ETABS API实战:导出框架元素数据到Excel 在结构工程师的日常工作中,经常需要从ETABS模型中提取框架元素信息进行后续分析。手动复制粘贴不仅耗时,还容易出错。今天我们来用简单的VBA代码实现自动化导出。 🎯 我们要实现什么? 一键点击,就能将ETABS中所有框架元素的基…...

Python训练营-Day26-函数专题1:函数定义与参数

题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一个名为 calculate_circle_area 的函数&#xff0c;该函数接收圆的半径 radius 作为参数&#xff0c;并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求&#xff1a;函数接收一个位置参数 radi…...