基于有限状态机开发健壮的Nodejs/TCP客户端
有限状态机
是一种数学计算模型,它描述了在任何给定时间只能处于一种状态的系统的行为。形式上,有限状态机有五个部分:
- 初始状态值 (
initial state
) - 有限的一组状态 (
states
) - 有限的一组事件 (
events
) - 由事件驱动的一组状态转移关系 (
transitions
) - 有限的一组最终状态 (
final states
)
状态是指由状态机建模的系统中某种有限的
、定性的
“模式”或“状态”,并不描述与该系统相关的所有(可能是无限的)数据。例如,水可以处于以下 4 种状态中的一种:冰、液体、气体或等离子体。然而,水的温度可以变化,所以其测量值是定量的和无限的。再比如管理TCP Socket
连接时,其生命周期内存在明显的有限状态转换。
可能有相当多的同学在开发中没意识到有限状态机
的作用,但是实际上,我们几乎无时不刻在有意无意间使用了有限状态机。当您在开发过程中能有意识地系统地进行有限状态分析并应用有限状态机,往往代表着您达到了较高的水平。
目前开源的有限状态机实现中比较知名的有:
xstate
:堪称状态机航空母舰,功能太强大了,也太复杂了,学习成本非常高。Javascript State Machine
:功能较弱,在实际试用过程中发现在进行异步切换时存在问题。jssm
:特点是引入自己的DSL语法来描述状态机,使用起来比较别扭。
事实上,从功能完整度上看xstate
是第一选择,但是其过于复杂了,在功能与易用平衡方面并不理想。
因此,我们开发了FlexState
有限状态机,力求在功能性、易用性上达到平衡。
FlexState
是一款简单易用的有限状态机,具有以下特性:
- 支持基于
Class
构建有限状态机实例 - 支持状态
enter/leave/resume/done
钩子事件 - 状态切换完全支持异步操作
- 支持定义异步状态动作
Action
- 支持状态切换生命周期事件订阅
- 支持错误处理和状态切换中止
- 基于
TypeScript
开发 - 支持子状态
- 核心代码
90%+
单元测试覆盖率
Github
官网
快速入门
下面我们以开发基于nodejs/net.socket
的TCP客户端为例来说明FlexStateMachine
的使用。
作为例子,我们为TCPClient
设计以下几种状态:
Initial
:初始状态,构建socket实例后处于该阶段。Connecting
:连接中,当调用Connect方法,触发connect事件前。Connected
:已连接,当触发connect事件后。Disconnecting
:正在断开,当调用destory或end方法后,end/close事件触发前。Disconnected
:被动断开,当触发end/close事件后。AlwaysDisconnected
: 主动断开状态IDLE
: 自动添加的空闲状态,状态机未启动时ERROR
: 自动添加的错误状态,特殊的FINAL
状态
TCPClient
的状态图如下:
第一步:构建状态机
推荐直接继承FlexStateMachine
来创建一个TCPClient
实例,该种方式更加简单易用。
import { state, FlexStateMachine } from "flexstate"class TcpClient extends FlexStateMachine{// 定义状态static states = { Initial : { value:0, title:"已初始化", next:["Connecting","Connected","Disconnected"],initial:true},Connecting : { value:1, title:"正在连接...", next:["Connected","Disconnected"] },Connected : { value:2, title:"已连接", next:["Disconnecting","Disconnected"] },Disconnecting : { value:3, title:"正在断开连接...", next:["Disconnected"] },Disconnected : {value:4, title:"已断开连接", next:["Connecting"]},AlwaysDisconnected : {value:5, title:"已主动断开连接", next:["Connecting"]}} constructor(options:FlexStateOptions){super(Object.assign({host:"",port:9000,autoStart:true,context : null, // 状态上下文对象,当执行动作或状态转换事件时的this指向autoStart : true, // 自动启动状态机timeout : 30 * 1000 // 当执行状态切换回调时的超时,如enter、leave、done回调injectActionMethod: true, // 将动作方法注入到当前实例中 },options)) } @state{when:["Initial","Disconnected","Error"], // 代表只能当处于此三种状态时才允许调用连接方法 pending:"Connecting", // 执行后进入正在连接中的状态}connect(){this._socket.connect(this.options) } @state({when:["Connected"], // 代表只有在已连接状态才允许执行断开方法pending:"Disconnecting"}) disconnect(){this._socket.destory()}// 当状态转换成功后会调用此方法ontTransition({error,from,to,done,timeConsuming}){console.log(`从<${previous}>转换到<current>,耗时:${timeConsuming}ms`) // 例 ==> 从<Connecting>转换到<Connected,耗时12ms>console.log(this.current) // {name,value,....}}onData(data){....}}
说明:
- 以上我们创建了一个继承自
FlexStateMachine
来创建一个TCPClient
实例 - 并且定义了
Initial
、Connecting
、Connected
、Disconnecting
、Disconnected
、AlwaysDisconnected
共六个状态以及状态之间的转换约束。同时,状态机还会自动添加一个ERROR
和IDLE
状态。 - 定义了
connect
和disconnect
两个动作action
,在这两个方法前添加@state
代表了当执行这两个方法会导致状态变化。
第二步:初始化TCPSocket
当实例化TCPClient
实例后,首先应该创建Socket实例。由于TCPClient
实例继承自FlexStateMachine
,并且我们指定了Initial
为初始化状态。
状态机会在实例化并启动后自动转换到Initial
状态。因此,我们可以在进入Initial
状态前进行初始化操作。
class TcpClient extends FlexStateMachine{// 转换至Initial状态前会调用方法async onInitialEnter({retry,retryCount}){try{ this._socket = new net.Socket()// 当连接成功时,切换到Connected事件; 每一个状态均有一个大写的状态值实例成员// this.CONNECTED==this.states.Connected.valuethis._socket.on("connect",()=>this.transition(this.CONNECTED)) this._socket.on("close",()=>{//.... 详见后续重连说明}) // 套接字因不活动而超时则触发,这只是通知套接字已空闲,用户必须手动关闭连接。// 通过事件触发方式来执行disconnect动作this._socket.on("timeout",()=>this.emit("disconnect"))this._socket.on("error",()=>this.transition(this.ERROR))this._socket.on("data",this.onData.bind(this))}catch(e){if(retryCount<3){retry(1000) // 1000ms后重试执行}else{ //throw e} }
}
当TCPClient
实例化,状态机处于IDLE
状态(<tcp实例>.current.name=='IDLE'
),然后状态机自动启动(autoStart=true
)将转换至Initial
状态(initial
状态)。
- 状态机转换至
Initial
状态前会调用onInitialEnter
。我们可以在此方法中创建TCP Socket
实例以及其他相关的初始化。 onInitialEnter
成功执行完毕后,状态机的状态将转换至Initial
。(IDLE
->Initial
)- 如果在
onInitialEnter
函数初始化失败或出错,则应该抛出错误。错误将导致状态机将无法转换至Initial
状态,也就无法进行后续的所有操作了。一般在初始化失败时,会进行如下操作:- 进行重试操作,直至初始化成功(即成功创建好Socket并进行相应的事件绑定)。
- 反复重试多次失败后,也可能会放弃重试,
TCP Client
将无法切换到Initial
状态,而是保持在IDLE
状态。 - 当条件具备时,状态机需要重新运行(即调用
tcp.start()
来启动状态机),将重复上述过程。
第三步:连接服务器
当TCPClient
实例初始化完成后,就可以开始连接服务器。我们可以在类上创建状态机动作connect
,启动连接操作。
import { state, FlexStateMachine } from "flexstate"class TcpClient extends FlexStateMachine{// 通过装饰器来声明这是一个状态动作 @state({// 代表只能当处于此三种状态时才允许执行动作,即调用连接方法when:["Initial","Disconnected","Error"], // 执行后进入正在连接中的状态pending:"Connecting" }) async connect(){ this._socket.connect(this.options) }
}
// 创建连接实例
let tcp = new TcpClient({...})
// 连接
tcp.connect()
// 状态机状态将变化: Initial -> Connecting -> Connected
// 如果连接出错状态将变化:Initial -> Connecting -> Error
上述的@state({....})
定义了一个状态机动作,代表当调用connect
方法时会导致一系列的状态转换:
- 动作名称为
connect
,会创建一个同名的实例方法tcp.connect
替换掉原始的connect
方法。 when
参数代表了只有当前状态为[Initial
、Disconnected
、Error
]其中一个时才允许执行connect
动作。pending="Connecting"
代表,执行connect
动作前,状态机的状态将暂时会切换至Connecting
,也就是会显示正在连接中
。由于连接操作可能是耗时的,所有设计一个正在连接中是比较符合实际业务逻辑的。- 如果执行
socket.connect({...})
出错,可以通过@state({retry,retryCount})
来启用重试逻辑。需要注意的是 调用connect
成功仅仅代表该方法在调用时没有出错,并不代表已经连接成功。是否连接成功需要由socket/connect
事件来触发确认。 - 在上述中,并没有显式指定当连接成功时的状态,原因是因为
connect
方法是一个异步方法,是否连接成功或失败是通过事件回调的方式转换状态的。在初始化阶段,我们订阅了close
、end
等回调。this._socket.on("close",()=>this.transition(this.DISCONNECTED))
this._socket.on("end",()=>this.transition(this.DISCONNECTED))
this._socket.on("error",()=>this.transition(this.ERROR))
当执行socket.connect
方法后,如果接收到close
/end
/error
则会转换到对应的DISCONNECTED
、ERROR
状态。
- 至此,实现了当
tcp.connect
方法,状态转换到Connecting
状态,连接成功转换至Connected
状态,连接被断开转换至Disconnected
状态,出现错误时转换到ERROR
状态。并且在出错时会进行一定重试操作,更多关于重试的内容详见后续介绍。
第四步:侦听连接状态
在TCP连接生命周期内,状态机会在最后Initial/Connecting/Connected/Disconnecting/Disconnected/AlwaysDisconnected
状态之间进行转换,我们希望可能侦听状态机的状态转换事件,以便在连接发生状态转换时进行一些操作,此时就可以侦听各种连接事件。
侦听连接状态有两种方法:
FlexStateMachine
本身就是一个EventEmitter,可以通过订阅事方式进行侦听。
// *****侦听某个状态事件*****tcp.on("Connected/enter",({from,to})=>{// 当准备进入连接前状态时触发此事件
}) tcp.on("Connected/leave",({from,to})=>{// 当准备要离开连接状态时触发此事件
}) tcp.on("Connected/done",({from,to})=>{// 当切换至连接状态后触发此事件
})
- 在类中也可以直接定义
on<状态名>Enter
、on<状态名>
、on<状态名>Done
、on<状态名>Leave
类方法来侦听事件。
class TcpClient extends FlexStateMachine{onInitialEnter({from,to}){...} // 进入Initial状态前onInitial({from,to}){...} // 已切换至Initial状态onInitialDone({from,to}){...} // ===onInitialonInitialLeave({from,to}){...} // 离开Initial状态时onConnectingEnter({from,to}){...} // 进入Connecting状态前onConnecting({from,to}){...} // 已切换至Connecting状态onConnectingDone({from,to}){...} // === onConnectingonConnectingLeave({from,to}){...} // 离开Connecting状态时onConnectedEnter({from,to}){...} // 进入Connected状态前onConnected({from,to}){...} // 已切换至Connected状态onConnectedDone({from,to}){...} // ===onConnectedonConnectedLeave({from,to}){...} // 离开Connected状态时//...所有状态均可以定义on<状态名>Enter、on<状态名>、on<状态名>Leave事件 }
第五步:断开重新连接
连接管理中的断开重连是非常重要的功能,要处理此逻辑,首先分析一下什么情况下会断开连接。
断开连接一般包括主动
和被动
两种情况:
- 服务器或网络问题等导致的连接断开
此种情况属于客户端被动断开连接,一般会需要进行自动重新连接。服务器主动断开时,客户端会侦听到end
事件,直接进入断开状态。即状态机不会切换到Disconnecting
,而是直接至Disconnected
。
- 客户端主动断开连接
此种情况属性客户主动断开连接发,就是客户端主动调用disconnect
方法,一般是不需要进行自动重连的。
主动断开时,需要调用socket.end
方法,然后等待end
事件的触发。状态机会经历从Disconnecting
到Disconnected
的过程。
无论是主动断开连接还是被动断开连接,均会触发close
事件,因此需要在close
事件触发时区别是主动断开还是被动断开。
为了更好地区别主动断开
和被动断开
,我们可以增加一个状态AlwaysDisconnected
来代表是客户端主动断开,AlwaysDisconnected
被设计为FINAL
状态。
当状态机切换到Disconnected
状态时调用connect
动作方法来重新连接。当状态机切换到AlwaysDisconnected
时,则不进行重新连接。
两者差别在于,如果是主动断开会经历Disconnecting
状态,而被动断开则不会经过此状态,因此我们就可以在on("close")
事件中处理将状态转换至AlwaysDisconnected
或DISCONNECTED
。
class TcpClient extends FlexStateMachine{class TcpClient extends FlexStateMachine{...// 转换至Initial状态前会调用方法async onInitialEnter({retry,retryCount}){// 在此需要确认该切换到Disconnected还是AlwaysDisconnected状态this._socket.on("close",()=>{// 主动调用disconnect方法时,状态机才会切换到Disconnectingif(this.current.name==="Disconnecting"){ this.transition(this.ALWAYSDISCONNECTED)}else{this.transition(this.DISCONNECTED)}})}// 当切换至Disconnected状态的回调async onDisconnected({from,to}){await delay(3000)this.connect() // 重新执行Connect动作}//async onConnectClosed({from,to}){}@state({when:"Connected",pending:"Disconnecting"// 由于调用end方法是异步操作,需要等待close事件触发后,才是真正的断开连接 // 因此,不能在调用disconnected返回后就将状态设置为AlwaysDisconnected// 也就是说不要在此配置rejected参数;// 假设执行this._socket.end没有出错,则状态将保持在Disconnecting状态,直至this._socket.on("close",callback)时才进行状态转换// rejected:"" })async disconnect(){// 注意:此操作是异步状态this._socket.end() }
}
第六步:连接认证子状态
当tcp连接成功后,一般服务器会要求对客户连接进行认证才允许进行使用,而认证操作(login/logout
)是一个耗时的异步操作,同样需要进行状态管理。当进入Connected
状态后,状态将在未认证
、正在认证
、已认证
三个状态间进行转换,并且在连接断开或者出错时马上退出这三个状态。因此,就有必要引入子状态的概念。
引入子状态后,对应的状态图更新如下:
class TcpClient extends FlexStateMachine{static states = { Connected : { value:2, title:"已连接", next:["Disconnecting","Disconnected","Error"] // 定义一个独立的状态机域scope:{states:{Unauthenticated : {value:0,title:"未认证",initial:true,next:["Authenticating"]},Authenticating : {value:1,title:"正在认证",next:["Authenticated"]}Authenticated : {value:2,title:"已认证",next:["Unauthenticated"]},}}}, } ......// 当状态机进入Connected后会启动其子状态机// 子状态机会转换到其初始状态Unauthenticated,然后就可以在此执行登录动作async onUnauthenticatedEnter({from,to}){this.login() // }onAuthenticated({from,to}){}@state({when:["Authenticating"],pending:["Authenticating"]})async login(){await this.send({// 认证信息})}@state({when:["Authenticated"] })async logout(){await this.send({// 注销信息}) }
}
推荐
以下是我的一大波开源项目推荐:
- 全流程一健化React/Vue/Nodejs国际化方案 - VoerkaI18n
- 无以伦比的React表单开发库 - speedform
- 终端界面开发增强库 - Logsets
- 简单的日志输出库 - VoerkaLogger
- 装饰器开发 - FlexDecorators
- 有限状态机库 - FlexState
- 通用函数工具库 - FlexTools
- 小巧优雅的CSS-IN-JS库 - Styledfc
- 为JSON文件添加注释的VSCODE插件 - json_comments_extension
- 开发交互式命令行程序库 - mixed-cli
- 强大的字符串插值变量处理工具库 - flexvars
- 前端link调试辅助工具 - yald
- 异步信号 - asyncsignal
相关文章:

基于有限状态机开发健壮的Nodejs/TCP客户端
有限状态机是一种数学计算模型,它描述了在任何给定时间只能处于一种状态的系统的行为。形式上,有限状态机有五个部分: 初始状态值 (initial state)有限的一组状态 (states)有限的一组事件 (events)由事件驱动的一组状态转移关系 (transition…...

javaEE13(网站第8章两个课后题)
1、对“jspservletjavabean实现分页查询”功能做如下补充: (1)记录批量删除:每个记录前添加复选框,点击批量删除,删除选中记录。 增加跳转到任意页功能。用户可改变每页记录条数。 页面&am…...

【Leetcode每日一题】 递归 - 反转链表(难度⭐)(35)
1. 题目解析 题目链接:206. 反转链表 这个问题的理解其实相当简单,只需看一下示例,基本就能明白其含义了。 2.算法原理 一、递归函数的核心任务 递归函数的主要职责是接受一个链表的头指针,并返回该链表逆序后的新头结点。递归…...

Unity基础学习
目录 基础知识点3D数学——基础Mathf三角函数坐标系 3D数学——向量向量模长和单位向量向量的加减乘除向量点乘向量叉乘向量插值运算 3D数学——四元数为何使用四元数四元数是什么四元数常用方法四元数计算 MonoBehavior中的重要内容延迟函数协同程序协同程序原理 Resources资源…...
Java并发编程学习笔记:AQS
Java并发编程学习笔记:AQS 一、底层原理核心功能同步状态管理CLH 队列和线程调度机制独占模式与共享模式模板方法设计模式自旋、阻塞与超时机制 运行流程 二、锁的公平性公平锁非公平锁 三、容器实现 JUC中的AQS(AbstractQueuedSynchronizer)…...

Github上哪些好用的工具
专注于web漏洞挖掘、内网渗透、免杀和代码审计,感谢各位师傅的关注!网安之路漫长,与君共勉! Qexo-爱写博客的师傅强烈推荐 漂亮的 Hexo 静态博客编辑器。该项目是基于 Django 的 Hexo 静态博客管理后台,支持文章管理、…...
如何确保面试流程标准化操作,避免人为因素影响**
一、背景 在招聘过程中,面试作为关键环节,其标准化操作至关重要。标准化不仅有助于提高面试效率和质量,还能减少人为因素的影响,确保公平、公正和客观。本文将从以下八个方面探讨如何确保面试流程的标准化操作。 二、明确面试标准 制定明确的面试标准和要求,确保所有面试…...

YOLOv7改进 | 更换主干网络之PP-LCNet
前言:Hello大家好,我是小哥谈。PP-LCNet是一个由百度团队针对Intel-CPU端加速而设计的轻量高性能网络。它是一种基于MKLDNN加速策略的轻量级卷积神经网络,适用于多任务,并具有提高模型准确率的方法。与之前预测速度相近的模型相比,PP-LCNet具有更高的准确性。此外,对于计…...

MySQL基础-----多表查询之子查询
目录 前言 子查询概述 1.概念 2.分类 一、标量子查询 二、列子查询 三、行子查询 四、表子查询 前言 上一期我们讲了内外连接查询以及自连接查询,那么本期我们就学习多表查询的子查询。本期会详细讲解什么是子查询,以及子查询的相关功能…...
nginx应用场景(附配置)
场景1:web服务器 server {listen 80;server_name example.com; # 替换为您的域名location / {root /data/wwwroot;index index.html index.htm;} }server {listen 443 ssl;server_name example.com; # 替换为您的域名ssl_certificate /path/to/certificate.crt;ssl…...
tvm android_rpc_test.py执行报错解决
执行 python3 tests/android_rpc_test.py 报错: Run CPU test ... Traceback (most recent call last): File "tests/android_rpc_test.py", line 129, in <module> test_rpc_module() File "tests/android_rpc_test.py", line …...

十、项目沟通管理
十、项目沟通管理 从马斯洛需求的各个层级上,都需要沟通的介入。如果缺乏沟通,甚至可能严重损伤身心健康。 沟通渠道 1、 规划沟通管理 规划沟通管理是基于每个相关方或相关方群体的信息需求、可用的组织资产,以及具体项目的需求&#x…...

SQL设计时增加说明列
后关闭sql Studio,然后打开注册表,注册表地址: 计算机\HKEY_CURRENT_USER\SOFTWARE\Microsoft\SQL Server Management Studio\18.0_IsoShell\DataProject 如有版本不同,红色内容有所变化,修改内容如下: SSVPropViewColumnsSQL70,SSVPropViewColumnsSQL80 全修改为 1,2,6,7…...

前端提高性能——使用Intersection Observer API对图片视频进行懒加载
前言 最近做了一个项目是类似于商城的,需要放很多图片,在用户选择一页五十条时,页面加载速度会比较慢。为了提高性能,选择用Intersection Observer API 实现图片懒加载。 实现步骤 一、html代码: <img class&qu…...

杂七杂八111
MQ 用处 一、异步。可提高性能和吞吐量 二、解耦 三、削峰 四、可靠。常用消息队列可以保证消息不丢失、不重复消费、消息顺序、消息幂等 选型 一Kafak:吞吐量最大,性能最好,集群高可用。缺点:会丢数据,功能较单一。 二Ra…...

微信小程序(一)
WebView app.是全局配置,app.json是全局配置文件,在页面的.json配置文件中的配置会覆盖我们全局的配置 快捷键: .box 敲回车 ----- <view class"box"></view> .row*8 敲回车: .row{$}*8 敲回车 案例1&…...

【DL经典回顾】激活函数大汇总(十二)(GLU ReGLU附代码和详细公式)
激活函数大汇总(十二)(GLU & ReGLU附代码和详细公式) 更多激活函数见激活函数大汇总列表 一、引言 欢迎来到我们深入探索神经网络核心组成部分——激活函数的系列博客。在人工智能的世界里,激活函数扮演着不可或…...

【C++】string类初步介绍
个人主页 : zxctscl 如有转载请先通知 文章目录 1. 为什么学习string类1.1 C语言中的字符串1.2 推荐学习网站 2. 标准库中的string类2.1 string类2.2 string类的常用接口说明2.2.1 constructor2.2.2 遍历string2.2.2.1 下标加[]遍历2.2.2.2 迭代器(itera…...

HCIP --- BGP 综合实验
目录 实验拓扑图: 实验要求: 实验步骤: 1.划分IP地址 R1的配置 R2的配置 R3的配置 R4的配置 R5的配置 R6的配置 R7的配置 R8的配置 2.检测查询IP地址 3.OSPF 建邻 4.查询OSPF 建邻是否成功 5.AS 2 内部BGP 建邻 a. AS 2 内部 IBG…...

【优选算法】专题1 -- 双指针 -- 移动零
前言: 📚为了提高算法思维,我会时常更新这个优选算法的系列,这个专题是关于双指针的练习 🎯个人主页:Dream_Chaser~-CSDN博客 一.移动零(easy) 描述: 「数组分两块」是⾮…...

centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...

Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...

vulnyx Blogger writeup
信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面,gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress,说明目标所使用的cms是wordpress,访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...

【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...

【堆垛策略】设计方法
堆垛策略的设计是积木堆叠系统的核心,直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法,涵盖基础规则、优化算法和容错机制: 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则: 大尺寸/重量积木在下…...
Python实现简单音频数据压缩与解压算法
Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中,压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言,提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...
React核心概念:State是什么?如何用useState管理组件自己的数据?
系列回顾: 在上一篇《React入门第一步》中,我们已经成功创建并运行了第一个React项目。我们学会了用Vite初始化项目,并修改了App.jsx组件,让页面显示出我们想要的文字。但是,那个页面是“死”的,它只是静态…...