1. 什么是有限状态机

有限状态机在百度百科上的解释为:
有限状态自动机(FSM “finite state machine” 或者FSA “finite state automaton” )是为研究有限内存的计算过程和某些语言类而抽象出的一种计算模型。有限状态自动机拥有有限数量的状态,每个状态可以迁移到零个或多个状态,输入字串决定执行哪个状态的迁移。有限状态自动机可以表示为一个有向图。

2. 有限状态机的应用场景

在实际工作中,经常遇到状态机的应用,尤其在网络协议部分,最为经典的就是TCP协议的状态机:

实际上,很多网络协议都是有工作状态的,不同阶段所处不同的状态,例如DHCP协议,路由协议,SIP协议等等。这些协议在实现的时候多多少少会用到状态机的原理,可能在实现上有较大的差别。我原来只是听说过有这个原理,但只限于听说,但具体实现以及它的优点更是不知所以;
这几天在学习BFD协议的时候,BFD的内核部分就是使用状态机来实现的,这就使我花点时间好好了解下状态机实现的原理,这篇博客主要介绍状态机的最简单实现,因为网上没有找到想要的实现,所以决定自己写几个实例并记录下来。
这几个实例是参照https://blog.csdn.net/zqixiao_09/article/details/50239337的栗子做的,只是这篇文章全是使用的switch-case实现的,所以我做一个补充,使用另一种更符合状态机的实现。
不是说有些问题必须使用状态机,只是使用状态机可以简化问题,代码的逻辑也比较清晰

3.状态机的基本框架

3.1 定义状态

如果要使用状态机进行解决问题,首先要确定这个问题本事有几个状态,然后我们把所用的状态做一个枚举类型的定义,问什么是枚举类型呢? 因为我们定义==状态的目的就是把状态当作索引值==,这可以说是状态机实现的精髓了,因为它就是根据当前的状态来确定下一步的行为。
例如:一个问题有三种状态,则可以使用这样的定义:

enum {
STATE_0 = 0, /* 数据下标从0开始,因此开始设置为0,后边逐渐递增*/
STATE_1,
STATE_2,
}FSM_STATE;

3.2定义事件

定义事件,这个不是必须的。 我是参照BFD内核源码的状态机实现来做实现的几个栗子,定义事件的好处就是可以统一处理各种情况,不需要再每个状态中再单独判断,这样需要花费更长的时间去实现每个状态中的函数。
事件是与状态相对应的,简单的说,从“状态0”切换到“状态1”是由某些特定事件导致的,同理从“状态1”切换到“状态2”是由另一个事件导致的。 我们把引起状态切换的条件称为事件
再举个栗子:
TCP服务器再收到客户端发来的SYN报文后,做出SYN+Ack的应答,我们就可以把==“收到SYN”称作一个事件==。
确定好事件后,我们将也定义一个事件的枚举类型,因为事件的作用就是作为另一个索引
例如:一个问题有三个事件,则可以使用这样的定义:

enum {
EVENT_NOT_SPACE=0, /*数据下标索引,从0开始*/
EVENT_SPACE,
EVENT_STR,
}FSM_EVENTS;

3.3定义状态机的结构体

因为我们已经知道状态机是有状态和事件构成的,因此状态机的结构体可以定义为 “动作”+“下一状态”
例如:

struct FSM_node{
void (*func)(char *); /*当前状态下,发生某个事件所采取的措施*/
int next_state; /*这个事件发生后,要切换的状态*/
};

当然这只是个结构体,而真实的状态机一般定义为一个二维的数组,为什么是二维的? 从上文已经知道“状态”+“事件”是同时作为索引值,因此这里定义为二维的变量也是可以理解的。
栗子中定义的状态机变量如下:

struct {
int (*func)(char *);
int next_state;
}FSM[STATE_NUM][EVENT_NUM]=
{
{/*current state isNotSpace : 状态0*/
{isNotSpace_func1,STATE_0}, /*NOT_SPACE:状态0时遇到一个非空格,打印 + 下一状态为状态0*/
{isNotSpace_func2,STATE_1}, /*IS_SPACE*:状态0时遇到一个空格,打印 + 下一状态为状态1 */
},
{/*current state isSpace : 状态1*/
{isSpace_func1, STATE_0}, /*NOT_SPACE:状态1时遇到一个非空格,打印 + 下一状态为状态0*/
{isSpace_func2, STATE_1}, /*IS_SPACE:状态1时遇到一个空格,不打印 + 下一状态为状态1*/
},
};

完整代码在第一个实例中。
FSM就是个二维数组,只不过它存储的是一个结构体;我们是在它定义的时候进行的初始化。
理论上,每一种状态下,所有的事件都有可能发生,所以每一种状态下,我们定义了(事件种类EVENT_NUM)个对象。

3.4 画状态变迁图

状态变迁图与流程图比较类似,只不过它突出是的状态和事件。画状态变迁图是为了开发方便,思路更加清晰。
画状态变迁图的好处是方便定义状态机:每一个状态下发生不同的事件分别该如何处理以及下一状态是什么。
我们再具体实例中说明。

4实例

4.1实例一

问题:除去一个字符串中连续的空格,只保留一个。
状态变迁图


代码实现

#include <stdio.h>
typedef enum{
STATE_0 = 0,
STATE_1,
}state;
typedef enum{
NOT_SPACE= 0,
IS_SPACE,
}events; #define STATE_NUM 2
#define DEVENT_NUM 2
int isNotSpace_func1(char *str)
{
putchar(*str);
}
int isNotSpace_func2(char *str)
{
putchar(*str);
}
int isSpace_func1(char *str)
{
putchar(*str);
}
int isSpace_func2(char *str)
{
; /*do nothing*/
}
struct {
int (*func)(char *);
int next_state;
}FSM[STATE_NUM][DEVENT_NUM]=
{
{/*current state isNotSpace*/
{isNotSpace_func1,STATE_0}, /*NOT_SPACE*/
{isNotSpace_func2,STATE_1}, /*IS_SPACE*/
},
{/*current state isSpace : 状态1*/
{isSpace_func1, STATE_0}, /*NOT_SPACE*/
{isSpace_func2, STATE_1}, /*IS_SPACE*/
},
};
/*根据当前状态和当前事件做索引,找到对应的处理情况;然后再获取下一状态*/
void demo1()
{
char str[] = "h e l l o, w o r l d;";
char *p = str;
int state_now = STATE_0;
int _event, ret;
while(*p){
if(*p==' ')
_event = IS_SPACE;
else
_event = NOT_SPACE;
/* FSM[state_now][_event]: 节点对象*/
/* (FSM[state_now][_event]).func : 该对象函数指针*/
/* *((FSM[state_now][_event]).func) : 获取到函数指针指向的函数*/
ret = (*((FSM[state_now][_event]).func))(p);
state_now = (FSM[state_now][_event]).next_state;
p++;
}
}

结果:

root# ./a.out
root# h e l l o, w o r l d;

4.2实例二

问题:除去一个字符串中连续的空格,只保留一个,但保留双引号(“ ”)内的所有字符。
状态变迁图

代码实现

#include <stdio.h>
enum {
STATE_0 = 0,
STATE_1,
STATE_2,
}FSM_STATE;
enum {
EVENT_NOT_SPACE=0,
EVENT_SPACE,
EVENT_STR,
}FSM_EVENTS;
#define STATE_NUM 3
#define EVENT_NUM 3
void func1(char *str)
{
putchar(*str);
}
void func2(char *str)
{
putchar(*str);
}
void func3(char *str)
{
putchar(*str);
}
void func4(char *str)
{
putchar(*str);
}
void func5(char *str)
{
;
}
void func6(char *str)
{
putchar(*str);
}
void func7(char *str)
{
putchar(*str);
}
void func8(char *str)
{
putchar(*str);
}
void func9(char *str)
{
putchar(*str);
}
struct {
void (*func)(char *);
int next_state;
}FSM_test2[STATE_NUM][EVENT_NUM]=
{
{//state_0 not space
{func1, STATE_0},
{func2, STATE_1},
{func3, STATE_2},
},
{//state_1 space
{func4, STATE_0},
{func5, STATE_1},
{func6, STATE_2}, },
{//state_2 ""
{func7, STATE_2}, /* when first " is reached, the state is always STATE_2, only when the next " reached, next state is STATE_0*/
{func8, STATE_2},
{func9, STATE_0},
},
}; void demo2()
{
char str[] = "h e l l o ,\"today is friday !!!\" w o r l d .";
char *p = str;
int ret;
int state = STATE_0;
int event;
while(*p){
if(*p==' ')
event = EVENT_SPACE;
else if(*p=='"')
event = EVENT_STR;
else
event = EVENT_NOT_SPACE;
//printf("\n-----state=%d-----event=%d------\n",state, event);
(*(FSM_test2[state][event].func))(p);
state = FSM_test2[state][event].next_state;
p++;
}
putchar(10);
}

结果:

root# ./a.out
root# h e l l o ,"today is friday !!!" w o r l d .

定义了很多同样的实现函数,只是为了说明每一种(状态,事件)都有各自的处理情景,这个栗子只是巧合

5结束

状态机的C语言基本实现框架就是这样,后续如果有经典的状态机实现,做更新处理。

C语言实现有限状态机的更多相关文章

  1. Python语言的有限状态机实现样例

    #!/usr/bin/env python3 class Connection(object): def __init__(self): self.change_state(ClosedConnect ...

  2. 状态机的c语言编程

    http://blog.csdn.net/shandongdaya/article/details/7282547 一 有限状态机的实现方式 有限状态机(Finite State Machine或者F ...

  3. C语言学习书籍推荐《C专家编程Expert C Programming Deep C Secrets》下载

    Peter Van Der Linden (作者) <C和C++经典著作 C专家编程Expert C Programming Deep C Secrets>展示了C程序员所使用的编码技巧, ...

  4. SQLMAP自注入--INJECTION TECGBUQUES FINGERPRINT

    -p参数 指定扫描的参数 ,使--level失效 -p“user-agent,refer”这些参数也可以通过-p来指定 sqlmap.py -u "http://127.0.0.1/muti ...

  5. 有限状态机(Finite-state machine, FSM)的C语言实现

    有限状态机,也称为FSM(Finite State Machine),其在任意时刻都处于有限状态集合中的某一状态.当其获得一个输入字符时,将从当前状态转换到另一个状态,或者仍然保持在当前状态.任何一个 ...

  6. JavaScript与有限状态机

    有限状态机(Finite-state machine)是一个非常有用的模型,可以模拟世界上大部分事物. 简单说,它有三个特征: * 状态总数(state)是有限的. * 任一时刻,只处在一种状态之中. ...

  7. C语言中的内存压缩技术

    C语言中的内存压缩技术 前言 在整个研究生阶段我都在参与一个LTE协议栈实现的项目,在这个项目中,我们利用一个自己编写的有限状态机框架将协议栈中每一层实现为一个内核模块.我们知道,在编写内核代码时需要 ...

  8. 用C语言实现有限状态自动机FSM

    摘要:状态机模式是一种行为模式,在<设计模式>这本书中对其有详细的描述,通过多态实现不同状态的调转行为的确是一种很好的方法,只可惜在嵌入式环境下,有时只能写纯C代码,并且还需要考虑代码的重 ...

  9. 基于Qt有限状态机的一种实现方式和完善的人工智能方法

    基于Qt有限状态机的一种实现方式和完善的人工智能方法 人工智能在今年是一个非常火的方向,当然了.不不过今年,它一直火了非常多年,有关人工智能的一些算法层出不穷.人工智能在非常多领域都有应用,就拿我熟悉 ...

随机推荐

  1. P6855「EZEC-4.5」走方格 TJ

    目录 前言 题意简述 法一:时间复杂度 $Θ(m2n2)$ (TLE) $Code$ 法二:正解,时间复杂度 $Θ(mn)$ $Code$ 写在最后 洛谷 前言 题目传送门 正解:动态规划 挺 dul ...

  2. shell的编程规范和变量

    目录 一.Shell脚本概述 1.shell脚本的概念 2.shell脚本应用场景 3.shell的作用--命令翻译器,"翻译官" 二.用户的登录shell 三.shell脚本的构 ...

  3. [解决方案]docker: Error response from daemon: OCI runtime create failed

    错误原因 在新服务器上安装好docker后,发现无法运行,经常一顿搜索后,发现是docker安装的版本过高,最新版本docker-18.06 的核心好像没有经过充分的测试就发布了. 导致一运行,就提示 ...

  4. 干了5年Android开发,突然感觉自己啥也不会,啥也不想干,还要继续吗?

    这是在某论坛看到的一名同行的吐槽: 我干了差不多5年,不过给人感觉跟只有两三年的人一样. 我觉得我不适合干程序员,主要是新东西的接受能力比其他人慢,Android技术又更新得很快,感觉总是跟不上.年纪 ...

  5. 使用 Service Worker 缓解网站 DDOS 攻击

    前言 传统的 DDOS 防御开销很大,而且有时效果并不好. 例如使用 DNS 切换故障 IP 的方案,由于域名会受到缓存等因素的影响通常有分钟级延时,前端难以快速生效.例如使用 CDN 服务,虽可抵挡 ...

  6. 从零开始实现简单 RPC 框架 2:扩展利器 SPI

    RPC 框架有很多可扩展的地方,如:序列化类型.压缩类型.负载均衡类型.注册中心类型等等. 假设框架提供的注册中心只有zookeeper,但是使用者想用Eureka,修改框架以支持使用者的需求显然不是 ...

  7. 在Linux系统上查找文件

    Find命令 格式:find <指定搜索范围> <指定条件> <指定动作> 其中搜索范围是一个目录名,指定条件包括文件名.文件属性(修改时间所属用户等).所在位置特 ...

  8. MySQL学习01(初识MySQL)

    初识MySQL 只会写代码的是码农:学好数据库,基本能混口饭吃:在此基础上再学好操作系统和计算机网络,就能当一个不错的程序员.如果能再把离散数学.数字电路.体系结构.数据结构/算法.编译原理学通透,再 ...

  9. Linux 文件、目录与磁盘格式

    文件属性      连接数  文件持有者 文件所属群组 文件容量 文件最后修改时间 文件名(就那个..) 第一栏其中文件属性有10,第一个属性代表这个文件是目录.文件或链接文件: [d]目录 [-]文 ...

  10. Linux 基础学习篇 序篇

    读序篇可以知道的: 1.有些指令知道前和知道后,自己的操作是完全不同的,可能知道前,会用reset把系统重新启动一遍,而知道后会使用ps和kill来关闭进程. 2.如果对Linus的学习知识" ...