嵌入式软件的设计模式与方法

思想有多远,我们就能走多远

4、状态与工作流类设计模式

4.1 状态与事件

行为随条件变化而改变,这里状态切换的模式也称为状态机。有限状态机 (Finite State Machine,FSM) 是由3 个主要元素组成的有向图: 状态、转换和动作。

状态是系统或者元素的状态;转换是从一个状态到另一个状态的路径,通常通过感兴趣的事件初始化,当元素处在前驱状态中,并且收到触发事件,它将连接前驱状态与后续状态。如果事件发生,然而状态中的元素没有对这个特定的事件做出响应,事件就“静静丢弃”没有任何效果,也就是状态机仅做肯定的陈述 (“当我在这个状态中并且这件事发生,我才会做” ) 。

状态机本身不关心事件如何到达,但是必须避免竞争条件。同步状态机必须阻塞调用者 (守卫调用模式或者临界区模式) ,异步状态机必须使用排队(队列模式)来存储它们的事件,直到它们得到处理。如果状态机正在执行动作的过程中产生新事件, 状态机会立即停止去处理事件? 答案当然是否定的。状态机在处理一个新事件之前,必须确保前一状态的处理完成。后续几个状态-事件模式都需要遵循这个原则,而且具体模式实现中不再赘述。

4.2 单事件接收器模式

单事件接收器状态机 (以下简称 SERSM) 简单说就是触发状态切换的事件接收入口只有一个,单事件接收器模式依赖于单一事件接收器在客户与状态机间提供接口。在内部,这个单一事件接收器必须接收事件数据类型,不仅识别哪个事件已经发生,并且识别任意伴随事件的数据。

事件接收器模式必然有事件,事件类型event_type和事件携带的数据event_data,前者可以是个枚举值,后者是一个结构体struct,为了识别不同的事件数据属性,可以选择union共用体更贴切。

如果是同步处理,状态执行比较简单,可以直接调用event_receptor,按传入的事件类型和事件数据执行并切状态;异步版本则建议组合使用队列模式,将事件按FIFO的方式入队,事件执行函数被动触发从队列中取出事件,用轮询的机制寻找新的事件和单事件接收器再执行,最终也是调用event_receptor。具体实现有两种方式:

1、分支逻辑法

利用 if-else 或者 switch-case 分支逻辑,按状态转移图,将每一个状态转移原模原样地直译成代码,对于简单的状态机来说,这种实现方式最简单、直接,是首选;缺点所有的状态逻辑封装在单一事件接收器内(一个大型 if-else 或 switch-case 结构),限制了状态变更和扩展性。

//代码只是表意,无法编译
//enum s1 s2 s3  状态枚举值
//state 表示当前状态
void event_receptor(event_type,event_data)
{
    switch(state)
    {
    case s1:
        state_handle1(event_type,event_data);
        break;
    case s2:
        state_handle2(event_type,event_data);
        break;
    case s3:
        state_handle3(event_type,event_data);
    default:
        break;
    }
}

state_handle1/state_handle2/state_handle3执行后,按执行情况切换状态到新的state。下次即使同样的事件,因为state不同,也会产生不同的效果。

2、查表法

对于状态很多、状态转移比较复杂的状态机来说,查表法比较合适,也称表驱动法。通过二维数组来表示状态转移图,能极大地提高代码的可读性和可维护性,扩展状态只需增加表即可。


//代码只是表意,无法编译
typedef struct
{
    int state;   //状态 enum
    pFun state_handle; //对应状态下的函数指针
}state_event_table_struct;

state_event_table_struct table[]={
    {s1,state_handle1},
    {s2,state_handle2},
    {s3,state_handle3}
};

void event_receptor(event_type,event_data)
{
    for(i=0;i<(sizeof(table)/sizeof(state_event_table_struct));i++)
    {
        //根据当前状态state查表选择对应的事件接收器,并进行状态切换
        if(current_state==table[i].state) 
        {
            table[i].state_handle(event_type,event_data);
            //current_state内部更新
        }
    }
}

4.3 多事件接收器模式

多事件接收器有限状态机 (MERSM) 通常仅用于同步状态机,这是因为业务层通常关心状态机的事件集合。在这个模式中,每个事件都有一个单一的事件接收器,每个事件接收器本身仅考虑处理单一事件以及执行相关动作。可以理解为单事件接收器是多个事件进入一个固定的接收器处理,而多事件接收器是多个事件组分配给多个接收器处理。

前者可以比喻为多个领导给一个员工安排任务,后者是多个领导同时给多个员工安排任务。在事件处理上可以复用处理机制,假设员工employee有A/B/C三人,任务task集有t1/t2/t3三种,其处理接口有多种实现方式。

1、拆分为单事件接收器模式内再嵌套一个单事件接收器


//代码只是表意,无法编译
void employee_task_handle(employee, task)
{
    switch(employee)
    {
        case A:
            {
                switch(task)
                {
                    case t1:
      //do something
                        break;
                    case t2:
      //do something
                        break;
                    case t3:
      //do something
                        break;
                    default:
                        break;
                }
            }
            break;

        case B:
   //同上
            break;
        case C:
   //同上
            break;
        default:
            break;
    }
}

对于两层switch-case,也可以先按task分,再嵌套按employee分类执行。

也可以直接将employee_task_handle 函数的参数拆分,例如 employee_A_handle/ employee_B_handle /employee_C_handle 三个函数体,可调用的事件接收入口就有3个,需要调用者自行区分,选择合适的事件接收接口,这也是多事件接收器字面意思的效果。

2、组合事件再按单事件接收器模式

employee三人与task三种,有9种组合方式,直接将有限的9种方式定为枚举值,就是个简单的单事件接收器模式了,只是代码处理上,每个case内重复的代码比较多而已。这只是适合组合类型比较少的情形,将多事件接收器模式降维度,简化为前一节的单事件接收器模式。

3、建立二维表

这种实现方式就是下一节的状态表模式。通过将状态逻辑分组,降为单事件接收器模式处理。例如MTK方案的锂电池脉冲充电管理,因为充电状态有很多状态,每个状态下充电是间歇性脉冲充电,充或者不充再分2个子状态,采用的正是这种方案。

本质上多事件接收器模式,可以采用单事件接收器模式或者状态表模式实现,这样的前提是事件参数类似,结构相同。如果不同事件传入的参数格式差异很大,很难统一;例如事件1需要2个int参数,事件2需要3个char数组为参数,直接按参数类型划分多个接口,不必强行参数封装统一。不同的事件处理分不同的接收器接口,也就是多事件接收器模式的特点。拆分为多个事件接收有利于传参,但要求使用者从多个接口中选择正确的。

4.4 状态表模式

状态表模式是没有嵌套状态机创建的模式,其效果类似表驱动法,状态表模式使用二维数组来存储状态转换信息,通常用状态--事件构建表格。状态表模式的状态间不存在逻辑关系,属于并行的扁平化状态,也就是任意状态在任意时刻的表现一致,状态切换与当前状态无关。

状态表模式的执行,直接通过当前的状态和事件组合来索引,调用前必须先初始化状态表。它也比其他模式更易于扩展,因为扩展只是按规则添加新元素到状态表。毕竟嵌入式设备,状态的类型必然是有限的,状态表可以使用静态方式存储,虽然浪费但实现简单。

//代码只是表意,无法编译
typedef void (* pfun_task_handle)(void);

pfun_task_handle employee_task_table[3][3]={
    {employeeA_task1,employeeA_task2,employeeA_task3},
    {employeeB_task1,employeeB_task2,employeeB_task3},
    {employeeC_task1,employeeC_task2,employeeC_task3},
};

//employee和task定为枚举值
//做好函数指针非空校验
void task_allocation(employee,task)
{
 employee_task_table[employee][task]();
}

这种模式非常适合同步状态机切换,如果是异步场景,最好使用队列模式组合处理。

4.5 分解与状态模式

前面的状态机中,元素始终正好处于某个状态之中,而且是一个确切的状态,这样的状态称为或状态。但实际场景也存在复杂的,例如交通信号灯,它有两个独立的属性:

颜色 :红Red、黄Yellow、绿Green  三种 

显示样式 :熄灭off 、长亮Steady、快闪Flashing quickly 、慢闪Flashing slowly 四种。

这个灯有10种(10=3x3+1)状态,因为off关闭状态,灯的颜色属性已经没必要参考了。这种独立状态乘到一起,形成一个可能巨大的状态集合很常见,解决这类问题的方法一一与状态。这里仅仅作阉割版介绍,状态机的状态,并不一定只是一个属性或枚举值解决,也可以是多个正交属性组合,这种状态需要多个参数来描述,可以在单事件接收器模式上增加状态参数。

理论与实际的差距,一直做GPS卫星定位器,设备含RGB三颗LED,3颗灯除独立工作指示特定状态外,还有组合状态,例三颗共同闪亮多次后恢复先前独立的闪亮状态,代码维护其实很复杂,扩展性并不好。关于状态机,如果状态之间有关联,存在优先级或者组合,不管什么模式代码都会比较难维护;只能说结合产品通用需求做个伪标准化接口。可见理论和实际还是存在较大差距的。

4.7 小结

状态机实现的模式,每一个都有优点和缺点,使用哪一个完全依赖于需求。

单事件接收器模式使用单一事件接收器,并且内部用大的 switch - case 语句实现状态行为。它需要创建并传递给接收器事件相关联的类型,最简单易用。

多事件接收器模式为每个事件使用单独的事件处理程序,以便事件类型不用明确指出,适合不同的参数类型匹配不同的事件接收器。

状态表模式可以扩展到大型的状态空间,并且提供与状态空间大小独立的性能,对较大状态空间支持较好。

分解与状态模式提供简单的实现与状态的方法(难通用待学习)。

5、安全性与可靠性类设计模式

安全性是指不会引起人或者设备危险的系统,即危险的严重性后果,和发生的可能性;可靠性用于衡量系统的“可服务时间”或者“可用性”。没有所谓的“安全软件”,因为嵌入式系统是电子、机械、软件在不同操作下的复合体 ,安全、稳定只是特定场合的运行结果。

嵌入式系统的安全性和可靠性,除去硬件防护方案外,软件上也可以采用一些防御性编程,实现系统的安全可靠以及异常恢复。主要从数据校验、备份两方面来入手。这里的解决方案其实也算是软件开发技巧,不是严格意义上的设计模式。

5.1 二进制反码模式

二进制反码模式在检测由于外界影响或者硬件故障内存损坏时很有用。

可能由 EMI (Electro-magnetic interference ,电磁干扰) 、热量、硬件故障、软件故障或者其他外部原因引发内存位损坏。这个模式将重要存储两份,一份以正常形式,而另一份以二进制反码〈~ 操作符计算位反转,逐位取反) 形式。读取数据时,二进制反码格式再次取反,并且与正常形式值比较。如果值完全相同则返回那个值,否则随之处理错误。

该模式提供可靠的方式识别影响单一内存分配的故障,非常适合数据量小但非常重要的数据存储,但对于非常大的数据结构,复制两份数据浪费硬件资源。在这种情况下使用数据流校验数据的正确性更合适。

一些非常关键的信息,如使用uint8_t变量表示某个状态,一般可设0和非0两种状态,假设原本的非0为1,但因为异常被改为2,软件是无能为力的。但如果使用二进制反码模式,使用0x55和0xAA为两种正常状态,其他为异常状态,这样软件的处理上就更加安全健壮。

5.2 数据流校验模式

数据流校验模式解决各种原因导致的变量损坏的问题,如环境因素 (EMI、热量、辐射) 、硬件因素(电源波动、内存单元故障、地址线短流路) 或者是软件故障 〈其他修改内存的软件错误) ,针对大型数据集合中的数据损坏问题。

简单说就是对数据进行校验,计算其和、CRC、MD5或者SHA哈希值等,如果数据中间出现异常被篡改,校验值可以发现错误,但是不能解决错误。考虑到硬件资源限制,一般用CRC16校验。将原始数据和其CRC16值一并存储。使用前通过校验值确认数据是否被篡改。

5.3 魔数标记模式

如果前面两种方式适合数据存储,如果只是单纯的内存数据块校验,可以简单粗暴的增加魔数标记。例如一个大结构体,首尾增加字段,正常情况下将其赋一个特殊值,如果使用中存在内存覆盖或者操作越界,导致首尾标记的数据出现变化,则表示内存出现严重问题。

typedef struct
{
    uint16_t magic_head;
    int32_t  importance1;
    uint8_t  importance2[5];
    uint16_t magic_tail;
} cutomer_data_struct;

//magic_head或magic_tail发生变化说明内存操作出现问题

对于动态内存申请也可以采用这种方式,期望申请N字节时多申请6个字节(举例而已),

magic_head申请长度有效堆区magic_tail
0x1234N实际可用区域0x1234

如果magic_head和magic_tail不是0x1234,说明动态申请的区域使用越界。可以参考动态内存管理及防御性编程。

有些芯片SDK代码,对flash的写保护,或者看门狗喂狗接口,其写法也类似,将一个特殊的值写给寄存器才算正常,也是基于这类考虑。魔法数在应用开发中尽量使用枚举值来替代,但也因为魔法数的特殊性,在安全方面可以避免误操作。

5.4 智能数据模式

软件为了正确执行功能都有前置条件,但是这些功能并没有明确地检查条件实际上是否满足,在合适的位置使用主动防卫的方式来检查参数,智能数据模式即为标量数据元素编写这种范例。

嵌入式C在函数层面,运行时对参数的范围不会检查,这是其固有的不安全性,需要使用者主动去对传入的参数进行范围检查,对函数的返回值进行结果判断。

智能数据模式简化就是对数据前置条件和规则的自检,属于习惯 (小模式)范围,创建或者启动时对参数自检,对传入的参数值进行范围检查,以及多个参数间的组合合理性检查,对运行的返回值进行错误处理。所有错误码以枚举类型展示,或者直接字符描述以使意图理解更加清晰。

智能数据模式优点是数据能自我保护,缺点是执行操作的性能开销 。一般只在针对核心功能、人机交互等,引入的错误容易产生严重后果的地方处理。

最典型最简单的应用场景就是对传入的指针参数进行非空判断,这里推荐合理的使用const限定参数。

5.5 单通道模式

通道模式使用中等规模或者大型的冗余来帮助识别何时发生运行时故障,并且可能 (依赖于怎样实现) 在故障存在时持续提供服务。

通道是体系结构,包含执行端到端处理的软件(可能有硬件),也就是说通过一系列的数据处理步骤,将关键部分独立化。例如数据流可以定为数据采集-处理-执行一条龙服务,基于事件的驱动。安全性和可靠性通过在通道的关键点增加检查得到增强,可能需要一些额外的硬件。由于仅有单一通道,因此该模式将在出现持续故障时,不能继续完成功能,但是它可检测并可能处理临时故障。

这里不提供代码范例,只是提供一种思路,关键部分单独处理,分步执行,不要耦合其他逻辑,对各步骤中的中间信息增加范围校验,识别异常并尽可能进行自我恢复处理。

5.6 双通道模式

双通道模式是一种通过提供多个通道提高稳定性的主要模式,从而在架构层解决冗余问题。比如体温计检测与显示,单通道到数据采集-处理-显示,双通道就可能是2颗传感器各自独立采集,再合并处理后显示。

如果通道是相同的(叫做同构冗余通道),能够解决随机故障 (偶尔失效) ,但是不能解决系统故障 (错误) 。如果通道使用不同的设计或者实现,称为异构冗余模式(也称为多样设计模式),能够解决随机和系统故障。

双通道模式对单个点 (失效或者是失效与错误,这依赖于选择的具体子模式) 故障提供保护。系统可能通过与另一个通道比较来检测一个通道中的错误,然后转换故障到安全状态,或者它可能使用其他的手段检测一个通道的故障,并且 当故障发生时,转换到另外一个。

这种双备份机制,通过复制通道以解决与安全性和可靠性相关的故障,通常也需要大量的硬件复制,以致非常高硬件成本。如果通道是相同的,则所有的复制品包含相同的错误,因此将在相同的环境下出现错误。一般是实施策略是两个通道的管理实现也不相同。两个通道能够同时运行,并且互相检查,如果在临界值上输出不同,系统则转换到故障安全状态。另外,一个通道可以运行直到检测到错误,并且开启另外一个通道,允许故障出现时持续提供服务。不同模式的变体,有不同的硬件成本和效果。

  1. 同构冗余模式   不同的通道使用相同的设计和实现,可以有效地解决单一点的错误,该模式变体有相对较高的生产成本 ,但是有相对低的设计成本 〈因为两个通道仅需设计一次,复制一次) 。

  2. 异构冗余模式   使用不同设计或者不同实现的双通道来解决随机和系统故障,系统能够在出现故障时持续提供服务。该模式变体不仅有相对较高的生产成本,而且有相对高的设计成本(因为两个个通道需设计两套方案) 。硬件、软件方案都不同,但独立且不同的方案解决随机和系统错误。

  3. 三模块冗余 (TMR ) 模式   使用相同设计的 3 个通道来解决故障,应用的理论是,如果有单一点故障,则通道中的一个将与另外两个解约,并且丢弃异常值。系统可以在出现故障时持续提供服务,提供故障在单一通道内适当的隔离。该模式有很高的生产成本,因为通道必须复制 3 次。如果所有的通道有相同的设计 (很常见) ,则设计成本相对低,如果通道的设计不同,则该模式变体的成本非常高,因为每个通道必须设计 3 次。这种在航空、军工电子学领域很常见的。

  4. 完整性检查模式   使用两个同构通道,一个是主执行通道,另一个使用低精确计算的轻量级通道,如果低精确检查通道检测到主通道有故障,则系统进入故障安全状态。该模式有低生产成本并且中等的设计成本,因为它需要额外的设计工作,但是有较低的精确冗余,当出现单一故障时不能继续提供服务。

  5. 监视器-执行器模式   使用两个额外同构通道,第一个就如在完整性检查模式那样,是一个执行通道,这个通道提供系统服务;第二个通道使用一个或多个独立的传感器监视执行通道的物理结果。如果执行通道有故障,并且执行不正确,则监视器通道识别它,并能够命令系统进入故障安全状态。如果监视器通道有故障,则执行器通道仍然执行正确行为。

多通道模式是个系统工程,不能仅依靠软件就实现,它是嵌入式设备安全和可靠性风险的最佳解决方案,但是成本也相应增加。一般的民用消费电子不会采纳。但是了解这些模式,也可能从软件方面、需求角度进行一定的优化。例如一般车载定位器检查汽车是否有行驶,可以依靠ACC点火,加速度传感器信息,GPS定位信息,虽然没有很明显的交代这是异构三通道,但设备本身支持这些信息的采集,软件层面上就可以组合这三种信息,合并分析得出汽车的状态。

5.7 小结

前面的模式在应用范围上 ,通常称为“设计定式”而不是“设计模式”,但合理的应用可提高设备在操作环境中的安全性和可靠性。

6、总结

天下武功,唯快不破。嵌入式设备因为其特殊性,物料更换、市场先机、订单交期、需求变更,都与软件开发存在关联,一般情况下,凡是软件能勉强解决的就不算增加成本,这种思路下软件开发就处于试验性开发、混乱下迭代的恶性循环,最终导致产品功能看起来都正常,而源码惨不忍睹。

实际上一个产品系列,开发很少奇技淫巧,更多的是修修补补、维护迭代,原创性开发不多;可阅读性和扩展性才是重点。而设计模式,就是在尽可能在局部采用特定的思路,去兼容不同的需求,让代码更好阅读,让下一个接手的人可以很容易的去支持更多奇葩需求。

PS

因为时间问题,嵌入式软件设计模式全文显得虎头蛇尾,下半章未使用源码范例具体说明,但实现的思路有描述清楚,设计模式本身就是重思想而不是套路。

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

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

相关文章

jsp教材管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 教材管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0&…

python常用的深度学习框架

目录 一&#xff1a;介绍 二&#xff1a;使用 Python中有几个非常受欢迎的深度学习框架&#xff0c;它们提供了构建和训练神经网络所需的各种工具和库。以下是一些最常用的Python深度学习框架&#xff1a; 一&#xff1a;介绍 TensorFlow&#xff1a;由Google开发的TensorF…

LeetCode-第171题-Excel表的序列号

1.题目描述 给你一个字符串 columnTitle &#xff0c;表示 Excel 表格中的列名称。返回 该列名称对应的列序号 。 例如&#xff1a; A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> 28 ... 2.样例描述 3.思路描述 遍历时将每个字母与 A 做减法&…

python 动态数据 展示 ,数据是由51单片机发送过来的,温度传感器。

import tkinter as tk import randomimport seriallis[] for i in range(50):lis.append(i1) # 打开串行端口 ser serial.Serial(COM3, 9600) # 9600为波特率&#xff0c;根据实际情况进行调整# 初始化数据 lis [random.randint(15, 35) for _ in range(50)]def update_data…

jenkins 发布远程服务器并部署项目

安装参考另一个文章 配置maven 和 jdk 和 git 注意jdk的安装目录&#xff0c;是jenkins 安装所在服务器的jdk目录 注意maven的目录 是jenkins 安装所在服务器的maven目录 注意git的目录 是jenkins 安装所在服务器的 git 目录 安装 Publish Over SSH 插件 配置远程服务器 创…

信号系统之线性系统详解

1 线性系统 信号描述了一个参数如何随另一个参数变化。例如&#xff0c;电子电路中的电压随时间变化&#xff0c;或图像中随距离变化的亮度。系统是响应输入信号而产生输出信号的任何过程。如图中的框图所示。 有几个规则用于命名信号&#xff1a; 连续信号使用圆括号&#x…

Python tkinter (15) —— PhotoImage

本文主要介绍Python tkinter PhotoImage图像应用及示例。 系列文章 python tkinter窗口简单实现 Python tkinter (1) —— Label标签 Python tkinter (2) —— Button标签 Python tkinter (3) —— Entry标签 Python tkinter (4) —— Text控件 Python tkinter (5) 选项按…

22.HarmonyOS App(JAVA)位置布局PositionLayout使用方法

不常用 在PositionLayout中&#xff0c;子组件通过指定准确的x/y坐标值在屏幕上显示。(0, 0)为左上角&#xff1b;当向下或向右移动时&#xff0c;坐标值变大&#xff1b;允许组件之间互相重叠 布局方式 PositionLayout以坐标的形式控制组件的显示位置&#xff0c;允许组件相…

JVM 性能调优 - Java 虚拟机内存体系(1)

Java 虚拟机我们简称为 JVM&#xff08;Java Virtual Machine&#xff09;。 Java 虚拟机在执行 Java 程序的过程中&#xff0c;会管理几个不同的数据区域。如下图所示&#xff1a; 下面我会介绍这几个数据区的特点。 堆 堆区的几个特点&#xff1a; 线程共享。启动时创建堆…

【MATLAB源码-第131期】基于matlab的淘金优化算法(GRO)机器人栅格路径规划,输出做短路径图和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 淘金优化算法&#xff08;GoldRush Optimizer&#xff0c;简称GRO&#xff09;是一种启发式优化算法&#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…

SpringBoot多模块项目proguard混淆

SpringBoot多模块项目proguard混淆 前言整活项目目录混淆后的效果图混淆配置混淆配置规则keep相关通配符和关键字keep说明常见问题解决办法效果前言 proguard 是压缩、优化和混淆Java字节码文件的免费的工具。 它可以删除无用的类、字段、方法和属性。可以删除没用的注释,最大…

网络故障的排错思路

一、网络排错必备知识 1、网络通信的基础设备和其对应的OSI层次 在网络通信中&#xff0c;了解基础设备如交换机、三层交换机、路由器和防火墙以及它们在OSI七层模型中 的作用至关重要。对于网络管理员和工程师来说&#xff0c;深入了解这些设备在OSI模型中的位置和功能可 …

探索Gin框架:Golang Gin框架请求参数的获取

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站https://www.captainbed.cn/kitie。 前言 我们在专栏的前面几篇文章内讲解了Gin框架的路由配置&#xff0c;服务启动等内容。 专栏地址&…

为什么有的人渐渐不点外卖了?

​随着互联网的发展和普及&#xff0c;外卖行业也在近几年内得到了迅猛的发展&#xff0c;它方便快捷、节省时间的特点使得外卖成为了很多人生活的一部分。但是&#xff0c;随着时间的推移&#xff0c;越来越多的人开始减少点外卖的频率&#xff0c;这是为什么呢&#xff1f; 首…

npm修改镜像源

背景&#xff1a;切换npm镜像源是经常遇到的事&#xff0c;下面记录下具体操作命令 1. 打开终端运行"npm config get registry"命令来查看当前配置的镜像源 npm config get registry2. 修改成淘宝镜像源"https://registry.npmjs.org/" npm config set re…

编译原理与技术(三)——语法分析(二)自顶向下-递归下降

一、语法分析的两种方法 自顶向下&#xff08;Top-down&#xff09;&#xff1a; 针对输入串&#xff0c;从文法的开始符号出发&#xff0c;尝试根据产生式规则推导&#xff08;derive&#xff09;出该输入串。 从根部开始构造语法树。 自底向上&#xff08;Bottom-up&#…

鸿蒙踩坑合集

各位网络中的小伙们&#xff0c;关于鸿蒙的踩坑陆陆续续收集中&#xff0c;本文章会持续更新&#xff0c;希望对您有所帮助 1、预览视图无法正常加载 重新编译项目&#xff0c;点击刷新按钮&#xff0c;控制台提示Build task failed. Open the Run window to view details. 解…

图书借阅管理系统

文章目录 图书借阅管理系统一、项目演示二、项目介绍三、万字文档参考四、系统部分功能截图五、部分代码展示六、底部获取项目和万字文档&#xff08;9.9&#xffe5;带走&#xff09; 图书借阅管理系统 一、项目演示 图书借阅管理系统 二、项目介绍 基于Springbootvue的前后…

二、SSM 整合配置实战

本章概要 依赖整合和添加控制层配置编写(SpringMVC 整合)业务配置编写(AOP/TX 整合)持久层配置编写(MyBatis 整合)容器初始化配置类整合测试 2.1 依赖整合和添加 数据库准备 数据库脚本 CREATE DATABASE mybatis-example;USE mybatis-example;CREATE TABLE t_emp(emp_id INT…