项目定位与服务器(SERVER)模块划分

news2024/11/26 0:52:10

目录

定位

HTTP协议以及HTTP服务器

高并发服务器

单Reactor单线程

单Reactor多线程

多Reactor多线程

模块划分

SERVER模块划分

Buffer 模块

Socket模块

Channel 模块

Connection模块 

Acceptor模块

TimerQueue模块

Poller模块

EventLoop模块

TcpServer模块

SERVER子块之间的连接

连接模块

EventLoop 模块

Acceptor模块


定位

在这个项目中,我们要实现的是一个高并发服务器组件,项目中通过组件的方式提供不同的应用层协议支持,以让使用者能够快速搭建指定应用层协议的一个高并发服务器。 当然,实际在项目中我们只提供了HTTP协议的支持。 

要注意的是,我们的服务器只是一个服务器组件,不提供具体的业务逻辑,业务逻辑需要组建的使用者根据需求自行设置。

那么我们的项目的重点就是实现两个模块或者说组件,一个是服务器的组件,一个是HTTP协议的组件。

在这里我们先了解服务器组件的模块划分。

HTTP协议以及HTTP服务器

HTTP协议(Hyper Text Transfer Protoccol)  ,超文本传输协议,他是一个应用层协议,是一种简单的请求-响应式协议,一般都是有客户端向服务器建立连接,并且发起请求,而服务器则根据客户端的请求将特定的资源返回给客户端或者完成特定的功能,而一般响应返回之后,连接就关闭了,当然HTTP也支持长连接

详细的HTTP的讲解在我们的Linux系统与网络专栏中有讲解,如果想要深入了解,可以前往

应用层协议 —— https-CSDN博客

HTTP协议是基于TCP协议的,所以我们所说的搭建一个HTTP服务器,其实是搭建一个TCP服务器,然后在服务器使用HTTP协议来解析请求,完成业务逻辑,以及构建响应返回。

简单来说,HTTP服务器其实就是以下四个步骤:

1 搭建一个TCP服务器,接收TCP连接

2 接收HTTP请求,进行请求的解析处理与响应

3 将处理结果组织成一个HTTP响应,通过TCP服务器返回给客户端

高并发服务器

实现一个HTTP服务器是十分简单的,但是实现一个高性能的支持高并发的服务器并不简单。而要实现一个高性能服务器,目前最常用的方式就是使用 Reactor 模型来搭建。
 

Reactor 模式简单理解就是 多路复用,仅用一个线程就能监听多个连接,哪个连接有数据输入或者说哪个连接接收到了发数据,就去处理该连接的事件。 他是一种事件驱动模式,只有在事件到来时,才会驱动服务器去处理。

而在Reactor线程监听到了对应的事件之后,就可以将数据分发给线程池进行业务逻辑的处理,这样就不需要多个线程一直在死循环等待IO就绪,能将系统资源让出来。

我们一般监听的事件就是 读事件,而读事件又分为两种,一种是读数据的事件就绪,另一种是监听套接字的读事件,其实就是底层有新的连接就绪,等待上层去获取。

Reactor 模型有三种:

单Reactor单线程

单Reactor单线程其实指的就是只由一个线程完成所有的连接的事件监控以及事件触发之后直接由该线程处理。 说白了就是单线程。那么在单线程情况下,不管是连接建立请求,还是数据处理的请求,都是在这一个线程完成的。

在这种模式下,所有的操作都在同一个线程内完成。

这种模式的优点就是设计简单,不会出现线程安全问题,所有的操作都是船形的。

但是他的缺点也很明显,由于是单线程,他无法发挥多核CPU的优势,性能上限不高。

但是并不是说他就一无是处,他适合那些客户端很少,且业务处理速度很快的场景。 

而如果业务处理很慢,那么他的串行化就会导致新连接来不及处理

单Reactor多线程

在这种模式下,我们使用一个 Reactor 线程专门用来监听事件,如果是新连接到来,那么在Reactor线程中进行连接的获取以及将新连接添加到多路复用模型中进行事件监控,而如果是数据到来,那么还是由Reactor线程进行数据的IO,然后将完整的请求交给线程池中的业务线程进行业务处理。 最终业务线程把数据请求完之后,将响应交给Reactor线程进行响应操作。

在这个工作模式下,能够利用多核CPU的优势,让不同线程并行推进进度,提高效率。

他的缺点也很明显,首先,多线程访问任务队列要做共享数据安全的保护。 同时,我们的Reactor线程承担了所有的IO事件(获取请求和响应请求)和连接事件,那么意味着,如果我们的服务器是一个高并发的服务器,也就是说,每一个时刻都有很多的客户端发送连接请求,而由于在这个模型中,IO和连接事件都是由一个Reactor线程处理的,也就是说,IO和连接事件的处理是串行的,那么也就意味着,IO的时候,我们是来不及进行新连接的获取的,由于IO的时间很长,最终可能导致大量连接来不及处理,无法满足高并发场景。

多Reactor多线程

上面的单Reactor多线程模型的最大的缺点就是高并发场景下,新连接可能来不及处理。而多Reactor多线程模型就是解决这个问题的。 

首先,我们只需要让一个单独的Reactor线程,专门用来获取新连接,这样就不会出现新连接来不及处理的问题。 而对于已经建立的连接的IO事件的监控,则交给其他的Reactor线程去完成。而这些Reactor线程读到完整的请求之后,还是将数据放到任务队列中,让我们的工作线程进行处理。

我们一般把这个负责获取新连接的Reactor线程称为主Reactor线程,而把这些负责具体的连接的事件监控的Reactor线程称为从属Reactor线程,最后,还是把负责业务处理的线程称之为工作线程。

但是上面的模型有一些很明显的缺点:

1 由于我们需要管理从属Reactor线程池和工作线程池,我们需要管理的共享资源都需要加锁,而加锁会导致效率的降低。

2 由于线程过多,CPU调度时线程切换的成本高。

那么要怎么解决呢?

一般在主从Reactor模式里面,从属Reactor线程之下不设置工作线程,而是直接由从属线程进行业务处理

那么这么做的好处是什么呢?首先,线程的数量显著减少了,CPU线程切换的次数或者说成本就降低了。 

同时,每一个连接对应的事件都只在一个Reactor线程中进行,那么对于一个连接的事件处理可以在线程中通过执行顺序的控制,保证一个连接的事件操作的安全。

而在我们的项目中要使用的就是主从Reactor模型,我们不需要实现工作线程池,直接在从属线程中进行事件的监控和处理。

从属Reactor线程要做三件事: 1、IO事件的监控 ;2、IO事件的处理;3、业务处理

模块划分

我们的项目中需要两个大模块,一个就是服务器模块,该模块用于实现一个主从Reactor模型的TCP服务器。 另一个就是协议模块,他用来给我们的服务器提供应用层的协议支持。在我们这个项目中就只提供HTTP协议的支持。

SERVER模块划分

我们的SERVER模块就是服务器模块,他需要管理所有的连接,以及管理我们所创建的所有的线程或者进程,我们需要保证他们安全高效的运行,最终达到高性能服务器的目标。

而具体的管理可以划分为三个部分:

1 对新连接的监听管理

2 对已建立的连接的管理

3 对超时连接的管理

对新连接的监听管理,其实就是管理我们的监听套接字,以及新连接建立之后的一系列操作,这需要由我们的主Reactor线程完成。

而队以建立的连接的管理则是监听已建立的连接的IO事件以及进行具体的业务处理,理所当然由从属Reactor线程完成。

对超时连接的管理其实就是对已建立的连接大的管理,也需要在从属Reactor线程中进行,为什么要进行超时管理呢?我们知道TCP协议是有自己的超时管理的,但是他的超时时间太长,是以小时为单位的,而我们要实现的服务器并不想让一些非活跃的连接来长期占用我们的资源,毕竟我们是要应对高并发的场景,如果一个连接不活跃,不如尽早把他的资源回收了来交给新的连接。

那么基于以上的管理思想,我们又可以将SERVER模块划分为以下的子模块:

Buffer 模块

Buffer模块其实就是一个用户态的缓冲区,那么对应每一个连接都需要配备两个缓冲区,接收缓冲区和发送缓冲区。 为什么我们有内核的缓冲区了,还需要提供用户级缓冲区? 这一点我们在网络的学习中应该也十分熟练了,因为我们从内核缓冲区读取出来的数据不一定就是一个完整的报文,那么我们就需要将已经读出来的数据继续保存。  于此同时,我们为什么要有发送缓冲区呢? 因为如果我们直接向内核缓冲区写的话,可能内核缓冲区的写事件没有就绪,那么也就会阻塞在写入操作,所以对用户的要发送的数据我们需要使用一个缓冲区来进行保存,使用缓冲区的原因也有一点就是不需要让用户在写入失败时再来考虑什么时候能写,而是把这些工作交给了我们的底层的事件监听以及事件处理来做。

而我们的缓冲区模块需要对外提供以供其他的模块调用调用的接口其实就只有两个,一个就是往缓冲区中写入数据,一个是从缓冲区中读取数据

Socket模块

Socket 模块其实就是对socket的封装,简化我们对套接字的操作,同时,我们还可以提供一些集成的功能,比如提供接口直接创建一个服务端的监听套接字,或者直接创建一个客户端连接服务端的通信套接字。

那么Socket的的操作其实就是对socket的操作的一些封装,让用户用起来更简单。

而Socket重点的接口或者常用的接口其实就是两个继承功能,以及发送和接受数据。

Channel 模块

Channel模块主要负责套接字所监听的事件的管理,以及就绪的事件的处理的回调函数的管理

每一个连接都需要对应一个 Channel 模块,Channel 模块中保存了该连接的套接字所需要监听的事件,同时Channel对象中也保存了各类事件的处理的回调方法。

那么具体有哪些事件呢?其实很简单,我们可以看一下 epoll 可以为我们监视的事件的类型,我们大致将其分为四类

第一类就是 读事件,EPOLLIN和EPOLLPRI都是数据到来,我们需要进行读取。 而EPOLLRDHUP则表示对端已经关闭套接字,准备关闭连接了,这时候其实我们也需要尽快将数据从内核缓存区读走。 这三个我们都归为读事件

第二类就是写事件,EPOLLOUT        

第三类是 挂断事件,EPOLLHUP,这个挂断和前面的EPOLLRDHUP的区别就是,EPOLLRDHUP是对端挂断,也就是对端关闭连接,而EPOLLHUP则是本端主动挂断,这时候我们也需要进行处理,比如将new出来的Channel对象释放,以及从其他的结构中拿走

第四类就是错误事件,错误事件发生之后我们其实也是需要释放对象和将对象从其他的模块中拿走。

第三类和第四类时间虽然有一定的区别,但是实际上我们的处理方法是类似的。

那么在与其他模块的关联上,重点接口就是四类事件的监控,以及四类事件的处理

Connection模块 

Connection 模块是对一个通信套接字的整体的管理,他内部其实会管理 Buffer,Socket,Channel对象,以及超时连接的处理等。每当我们Accept获取到一个新链接的时候,其实就需要创建一个Connection对象,而Connection 对象进行创建的时候,会对内部的三个子模块进行初始化设置,比如 Channel 内部的四个回调函数,就是有Connection传进去的。

同时Connection模块也需要包含几个上层设置的回调函数: 连接建立时需要执行的回调函数,连接关闭时执行的回调函数,有一个新数据时执行的回调函数,以及事件就绪时的回调函数

而Connection模块还需要提供协议切换/升级的功能,这个功能其实就是对协议上下文以及Connection的四个上层回调方法的重新设置。

他的主要接口其实分为三个部分,一类是对外提供的接口,供其他模块调用,一类是封装内部模块的操作的接口,还有一类是由上层模块设置的事件回调,当然这一类其实是算在他的成员,严格意义上来说并不是他的接口,不过为了理解他与上层模块的联系,我们还是将其罗列出来。

Acceptor模块

Acceptor模块是对监听套接字的Socket和Channel模块的一个整体管理,他实现的是对监听套接字的管理。

他的主要操作就是获取新连接,然后通过内部设置的回调方法将新连接封装成一个Connection对象,而他的可读事件的处理其实就是 accept 并创建Connection对象交给上层。

TimerQueue模块

TimerQueue简单理解就是对超时连接进行管理,更好的理解就是用于设置定时任务,以及管理定时任务。相当于一个定时任务的管理器,我们可以往里面添加一些定时任务,同时也可以刷新定时人物的超时事件,以及取消定时任务等等。

这个模块在我们项目中主要的作用就是对Connection对象的超时管理,也就是对超时连接的资源释放

Poller模块

Poller模块就是对epoll及其操作的封装,是直接进行事件监控的模块,需要完成监控事件的添加,移除,以及对描述符移除监控,以及获取就绪事件等。

EventLoop模块

EventLoop模块其实就是我们的主从Reactor模型中的从属Reactor模块,它是对Poller,TimerQueue,Socket模块的整体封装,负责所有的描述符的事件监控。

每一个EventLoop模块其实都需要对应一个线程,每一个连接创建出来之后,都需要关联或者说绑定一个EventLoop线程,从此之后,对于该连接的所有操作都需要在这个EventLoop线程中完成,以保证操作的线程安全。 不能出现说其中一个线程正在进行操作,另一个线程就把连接关闭了。 把对同一个连接的所有操作放在同一个线程中执行,他们就是串行化的,能避免出现线程安全问题。

EventLoop需要进行实际的监控和事件处理,那么他需要关联一个Poller模块,每一个EventLoop模块内部都需要一个Poller来进行事件的监控,每一个连接和对应的EventLoop也需要互相关联,EvenetLoop中才能调用Connection中的Channel模块进行事件的处理。

同时,EventLoop模块还需要保证内部的所有的连接的活跃性,所以他还需要包含一个TimerQueue模块,来进行超时连接的资源释放工作。

那么EventPool需要对外提供的重要接口其实大部分都是对子模块的封装,比如添加事件监控,移出事件监控,更新/修改时间监控(取消某个时间的监控都算在更新事件监控中)。 以及添加定时任务,刷新定时任务,取消定时任务, 还有对四类就绪事件以及任意事件的处理。

同时,由于同一时间可能会有很多事件就绪,IO事件完成之后,还需要进行一些其他的操作,而EventLoop为了提高效率,高优先级的先把所有的IO事件处理完成,而将对连接的其他的操作,比如数据处理,从用户缓冲区读取和发送数据等等操作放入一个任务队列中,而后对任务队列中的所有的任务按顺序执行。

重要接口 :

TcpServer模块

TcpServer模块是所有管理模块的整合,它内部主要就是Acceptor模块和我们的从属Reactor线程池,也就是EventLoop线程池,这个模块是提供给用户使用的,提供一些接口让用户能够简易搭建一个服务器,以及进行一些设置。

这个模块是在最后所有的小模块都设计完了,再对Acceptor和EventLoopPool模块进行一个封装。重点工作其实还是在其他的子模块,他只是对其他模块提供的功能的封装。

SERVER子块之间的连接

连接模块

首先,最重要的就是 Connection模块与他的子模块的关系,因为Conection是我们整个项目中设计最复杂的模块,同时也是功能最复杂的模块,我们就拿他的几个重要的接口来举例:

首先对于四个向外提供的机构来说,关闭连接,释放资源这一点,其实TcpServer模块是会对所有的Connection对象进行管理的,在实现关闭连接释放资源的时候,是会调用TcpServer给Connection设置的一个回调函数的。

而对于发送数据,Connection提供的发送数据是将数据拷贝到发送缓冲区,因为我们并不能保证调用发送时,我们套接字的内核缓冲区一定就能够进行写入,一定要等到写事件就绪才会真正的发送

而协议切换,主要有两个方面,一个就是我们Connection中会保存数据处理的上下文,这是跟协议强相关的,比如我们使用Http协议的话,我们的上下文中就会保存Http协议的报头中的各个字段,来表示当前处理到哪一个阶段了,那么如果这一次读取没有读到一个完整的报文,我们可以将这个进度保存在上下文中,那么下一次读事件再到来,有新数据接受的时候,我们就可以直接从上下文保存的进度开始继续读取,提高效率。那么协议在进行切换的时候,就需要将上下文数据进行替换。 除了上下文的替换之外,与协议强相关的还有TcpServer给Connection设置的四个回调方法,这四个回调方法就是不同协议该如何处理的区别所在,尤其是接收新数据之后的回调,当然这些方法是由用户设置,协议切换时,用户也需要传入新的数据和新的上下文结构数据。 

而启动和取消超时释放策略,测试依赖于TimerQueue所提供的接口来完成的,当启动超时策略时我,我们就会添加/刷新(有可能上次取消超时策略之前设置的定时任务还在时间轮中)一个当前连接的定时关闭任务,而取消超时策略就是将Connection的定时任务通过TimerQueue提供的接口取消掉。同时,一个连接是否需要启动超时策略也是由TcpServer或者用户来设置的。

而Connection中的接收数据的方法,其实是作为Channel中的读事件的回调执行的,由Connection提供,而接收数据最终也是将数据缓存到Connection中的Buffer缓冲区中。 

而发送数据也是由Channel中的写事件触发的,写事件就绪时,会调用由Connection设置的写事件回调,将数据从Buffer缓冲区写到内核缓冲区中。 

关闭套接字接口也是由Channel事件触发的,但是这个关闭套接字接口其实只会将该套接字的监听移除,而整个连接的释放其实会在所有数据处理完之后进行

刷新活跃度的接口其实就是通过调用TimerQueue中提供的接口来完成的。

剩下的就是用户设置给TcpServer,而TcpServer设置给Connection 的四个回调方法。 建立连接回调,会在Acceptor获取到一个新连接,并将其封装成一个Connection,建立完连接时进行调用。

而用户设置的接收数据的回调,会在我们的Channel的读事件回调中进行一次调用,其实就是在Connection提供的读接口中。

而关闭连接回调也是在Connection设置给Channel的关闭连接事件的回调中调用。

任意事件回调也是一样的,会在Connection提供的,设置给Channel的任意事件回调用进行调用,也就是在刷新定时器之后进行一次调用。

EventLoop 模块

其实对连接的所有的操作都会由EventLoop对应的线程来完成,EventLoop是操作的真正的执行者,Connection等模块其实并不会真正执行操作,只有交给EventLoop线程执行才会保证线程安全。

而在这里我们就重点关注三个小模块,分别是TimerQueue模块,Channel模块以及Poller模块。

首先对于事件的管理,其实EventLoop中对于事件的处理的操作都是封装了Poller的接口,而Channel在监控的事件发生变化时,也会调用EventLoop中的接口进行操作,最终本质还是调用了Poller的接口操作。

而事件就绪之后,调用处理方法,其实不是直接调用,因为未来我们是使用线程池来进行事件监控和获取的,取出这个事件的并不一定就是关联的EventLoop线程,所以未来我们进行操作时,如果是EventLoop获取的,那么可以直接执行,而如果不是,那么会放到EventLoop线程的任务队列中。

而EventLoop对于连接的定时任务的管理也是使用TimerQueue提供的接口完成的。

Acceptor模块

Acceptor模块就是我们的主Reactor线程,他负责接受新连接,同时要将新连接封装成一个Conencton对象,添加到EventLoop的事件循环模块中。

以上就是我们的项目中的服务器组件的基本的模块划分、各个模块的主要功能,以及模块之间的大致关系。

下一篇文章会介绍几个项目中会用到的重要的知识点或者技术。

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

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

相关文章

【ADC】噪声(1)噪声分类

概述 本文学习于TI 高精度实验室课程,总结 ADC 的噪声分类,并简要介绍量化噪声和热噪声。 文章目录 概述一、ADC 中的噪声类型二、量化噪声三、热噪声四、量化噪声与热噪声对比 一、ADC 中的噪声类型 ADC 固有噪声由两部分组成:第一部分是量…

【树莓派系列】树莓派wiringPi库详解,官方外设开发

树莓派wiringPi库详解,官方外设开发 文章目录 树莓派wiringPi库详解,官方外设开发一、安装wiringPi库二、wiringPi库API大全1.硬件初始化函数2.通用GPIO控制函数3.时间控制函数4.串口通信串口API串口通信配置多串口通信配置串口自发自收测试串口间通信测…

Django 后端数据传给前端

Step 1 创建一个数据库 Step 2 在Django中点击数据库连接 Step 3 连接成功 Step 4 settings中找DATABASES Step 5 将数据库挂上面 将数据库引擎和数据库名改成自己的 Step 6 在_init_.py中加上数据库的支持语句 import pymysql pymysql.install_as_MySQLdb() Step7 简单创建两…

以企业的视角进行大学生招聘

课程来源:中国计算机学会---朱颖韶(资深人力资源领域--HR) 一、招聘流程 1.简历->门槛 注重:专业学历、行业经验 2.笔试面试->专业知识与技能 3.简历面试-> 过往的成果 4.面试 沟通能力、学习力-----了解动机、价值观…

Pikachu-Sql Inject-insert/update/delete注入

insert 注入 插入语句 insert into tables values(value1,value2,value3); 如:插入用户表 insert into users (id,name,password) values (id,username,password); 当点击注册 先判断是否有SQL注入漏洞,经过判断之后发现存在SQL漏洞。构造insert的pa…

8644 堆排序

### 思路 堆排序是一种基于堆数据结构的排序算法。堆是一种完全二叉树,分为最大堆和最小堆。堆排序的基本思想是将待排序数组构造成一个最大堆,然后依次将堆顶元素与末尾元素交换,并调整堆结构,直到排序完成。 ### 伪代码 1. 读取…

自闭症干预寄宿学校:专业治疗帮助孩子发展

自闭症干预寄宿学校:星贝育园的专业治疗助力孩子全面发展 在自闭症儿童的教育与康复领域,寄宿学校以其独特的教育模式和全面的关怀体系,为众多家庭提供了重要的选择。广州星贝育园自闭症儿童寄宿制学校,作为这一领域的佼佼者&…

达梦core文件分析(学习笔记)

目录 1、core 文件生成 1.1 前置条件说明 1.2 关于 core 文件生成路径的说明 1.3查看 core 文件的前置条件 2、查看 core 文件堆栈信息 2.1 使用gdb 2.2 使用达梦dmrdc 3、core 分析过程 3.1 服务端主动 core 3.2因未知异常原因导致的 core 4、测试案例 4.1测试环境…

(十八)、登陆 k8s 的 kubernetes-dashboard 更多可视化工具

文章目录 1、回顾 k8s 的安装2、确认 k8s 运行状态3、通过 token 登陆3.1、使用现有的用户登陆3.2、新加用户登陆 4、k8s 可视化工具 1、回顾 k8s 的安装 Mac 安装k8s 2、确认 k8s 运行状态 kubectl proxy kubectl cluster-info kubectl get pods -n kubernetes-dashboard3、…

网页前端开发之Javascript入门篇(4/9):循环控制

Javascript循环控制 什么是循环控制? 答:其概念跟 Python教程 介绍的一样,只是语法上有所变化。 参考流程图如下: 其对应语法: var i 0; // 设置起始值 var minutes 15; // 设置结束值(15分钟…

Stream流的终结方法(一)

1.Stream流的终结方法 2.forEach 对于forEach方法,用来遍历stream流中的所有数据 package com.njau.d10_my_stream;import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.function.Consumer; import java.util…

安全帽头盔检测数据集 3类 12000张 安全帽数据集 voc yolo

安全帽头盔检测数据集 3类 12000张 安全帽数据集 voc yolo 安全帽头盔检测数据集介绍 数据集名称 安全帽头盔检测数据集 (Safety Helmet and Person Detection Dataset) 数据集概述 该数据集专为训练和评估基于YOLO系列目标检测模型(包括YOLOv5、YOLOv6、YOLOv7…

SpringCloud入门(十一)路由过滤器和路由断言工厂

一、路由过滤器 路由过滤器( GatewayFilter )是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理: 如图:网关路由过滤器: 路由过滤器的作用是: 1.对路由的请求或响应做加…

第二十章(自定义类型,联合和枚举)

1. 联合体类型的声明 2. 联合体的特点 3. 联合体⼤⼩的计算 4. 枚举类型的声明 5. 枚举类型的优点 6. 枚举类型的使⽤ 光阴如骏马加鞭一、联合体 概念:像结构体一样,联合体也是由一个或者多个成员组成的,这些成员也可以是不同的类型。 …

JavaSE篇:文件IO

一 认识文件 在硬盘这种持久化存储的I/O设备或其他存储介质中 ,当我们想要进行数据保存时,往往不是保存成⼀个整体,⽽是独⽴成⼀个个的单位进⾏保存,这个独⽴的单位就被抽象成⽂件的概念。就类似办公桌上的⼀份份真实的⽂件⼀般。…

疾风气象大模型如何预测气象数据,预测数据怎么获得

随着科技的快速发展,人工智能和大数据技术逐渐渗透到各个领域,气象预测也不例外。过去,气象预测主要依赖于物理模型,结合大气、海洋、陆地等系统的观测数据,通过复杂的数值计算来推测未来天气。而如今,大模…

八、跳跃、闪避

一、人物跳跃功能 1、动画 设置一个bool值 条件设置为true 2、逻辑 实现跳跃,一定有IsGround;判断是否为地面,进行跳跃功能 写一个跳跃和一个条约结束方法 跳跃设置为false,结束设置为true 3、代码 public void Jump() {if…

Ray_Tracing_In_One_Weekend下

1Lambertian漫反射材质 一个物体的材质,可以分成两部分来看,因为物体没有绝对光滑和绝对粗糙 漫反射:由于物体粗糙,那么对于微小平面,光线会向四周反射,光源的一部分光线传回人眼 镜面反射:假…

C++ 类和对象的初步介绍

文章目录 1.面向过程和面向对象的初步认识2.类的引入3.类的定义4. 类的访问限定符及封装4.1 访问限定符4.2 封装 5.类的作用域6.类的实例化 1.面向过程和面向对象的初步认识 C语言是面向过程的,关注的过程,分析出求解问题的步骤,通过函数调用…

OpenCV Canny()函数

OpenCV Canny()函数被用来检测图像物体的边缘。其算法原理如下: 高斯滤波:使用高斯滤波器平滑图像以减少噪声。高斯滤波器是一种线性滤波器,可以消除图像中的高频噪声,同时保留边缘信息。计算梯度强度和方向:使用Sobe…