有限状态机FSM的实现与理解

发布时间 - 2017-06-25 00:00:00    点击率:

有限状态机(finite state machine)简称fsm,表示有限个状态及在这些状态之间的转移和动作等行为的数学模型,在计算机领域有着广泛的应用。fsm是一种逻辑单元内部的一种高效编程方法,在服务器编程中,服务器可以根据不同状态或者消息类型进行相应的处理逻辑,使得程序逻辑清晰易懂。

那有限状态机通常在什么地方被用到?

处理程序语言或者自然语言的 tokenizer,自底向上解析语法的parser,
各种通信协议发送方和接受方传递数据对消息处理,游戏AI等都有应用场景。

状态机有以下几种实现方法,我将一一阐述它们的优缺点。

一、使用if/else if语句实现的FSM

使用if/else if语句是实现的FSM最简单最易懂的方法,我们只需要通过大量的if /else if语句来判断状态值来执行相应的逻辑处理。

看看下面的例子,我们使用了大量的if/else if语句实现了一个简单的状态机,做到了根据状态的不同执行相应的操作,并且实现了状态的跳转。

//比如我们定义了小明一天的状态如下
enum
{
    GET_UP,
    GO_TO_SCHOOL,
    HAVE_LUNCH,
    GO_HOME,
    DO_HOMEWORK,
    SLEEP,
};


int main()
{
    int state = GET_UP;
    //小明的一天
    while (1)
    {
        if (state == GET_UP)
        {
            GetUp(); //具体调用的函数
            state = GO_TO_SCHOOL;  //状态的转移
        }
        else if (state == GO_TO_SCHOOL)
        {
            Go2School();
            state = HAVE_LUNCH;
        }
        else if (state == HAVE_LUNCH)
        {
            HaveLunch();
        }
        ...
        else if (state == SLEEP)
        {
            Go2Bed();
            state = GET_UP;
        }
    }

    return 0;
}

看完上面的例子,大家有什么感受?是不是感觉程序虽然简单易懂,但是使用了大量的if判断语句,使得代码很低端,同时代码膨胀的比较厉害。这个状态机的状态仅有几个,代码膨胀并不明显,但是如果我们需要处理的状态有数十个的话,该状态机的代码就不好读了。

二、使用switch实现FSM

使用switch语句实现的FSM的结构变得更为清晰了,其缺点也是明显的:这种设计方法虽然简单,通过一大堆判断来处理,适合小规模的状态切换流程,但如果规模扩大难以扩展和维护。

int main()
{
    int state = GET_UP;
    //小明的一天
    while (1)
    {

        switch(state)
        {
        case GET_UP:
            GetUp(); //具体调用的函数
            state = GO_TO_SCHOOL;  //状态的转移
            break;
        case GO_TO_SCHOOL:
            Go2School();
            state = HAVE_LUNCH;
            break;
        case HAVE_LUNCH:
            HaveLunch();
            state = GO_HOME;
            break;
            ...
        default:
            break;
        }
    }

    return 0;
}

三、使用函数指针实现FSM

使用函数指针实现FSM的思路:建立相应的状态表和动作查询表,根据状态表、事件、动作表定位相应的动作处理函数,执行完成后再进行状态的切换。

当然使用函数指针实现的FSM的过程还是比较费时费力,但是这一切都是值得的,因为当你的程序规模大时候,基于这种表结构的状态机,维护程序起来也是得心应手。

下面给出一个使用函数指针实现的FSM的框架:

我们还是以“小明的一天”为例设计出该FSM。

先给出该FSM的状态转移图:

下面讲解关键部分代码实现

首先我们定义出小明一天的活动状态

//比如我们定义了小明一天的状态如下
enum
{
    GET_UP,
    GO_TO_SCHOOL,
    HAVE_LUNCH,
    DO_HOMEWORK,
    SLEEP,
};

我们也定义出会发生的事件

enum
{
    EVENT1 = 1,
    EVENT2,
    EVENT3,
};

定义状态表的数据结构

typedef struct FsmTable_s
{
    int event;   //事件
    int CurState;  //当前状态
    void (*eventActFun)();  //函数指针
    int NextState;  //下一个状态
}FsmTable_t;

接下来定义出最重要FSM的状态表,我们整个FSM就是根据这个定义好的表来运转的。

FsmTable_t XiaoMingTable[] =
{
    //{到来的事件,当前的状态,将要要执行的函数,下一个状态}
    { EVENT1,  SLEEP,           GetUp,        GET_UP },
    { EVENT2,  GET_UP,          Go2School,    GO_TO_SCHOOL },
    { EVENT3,  GO_TO_SCHOOL,    HaveLunch,    HAVE_LUNCH },
    { EVENT1,  HAVE_LUNCH,      DoHomework,   DO_HOMEWORK },
    { EVENT2,  DO_HOMEWORK,     Go2Bed,       SLEEP },

    //add your codes here
};

状态机的注册、状态转移、事件处理的动作实现

/*状态机注册*/
void FSM_Regist(FSM_t* pFsm, FsmTable_t* pTable)
{
    pFsm->FsmTable = pTable;
}

/*状态迁移*/
void FSM_StateTransfer(FSM_t* pFsm, int state)
{
    pFsm->curState = state;
}

/*事件处理*/
void FSM_EventHandle(FSM_t* pFsm, int event)
{
    FsmTable_t* pActTable = pFsm->FsmTable;
    void (*eventActFun)() = NULL;  //函数指针初始化为空
    int NextState;
    int CurState = pFsm->curState;
    int flag = 0; //标识是否满足条件
    int i;

    /*获取当前动作函数*/
    for (i = 0; i

主函数我们这样写,然后观察状态机的运转情况

int main()
{
    FSM_t fsm;
    InitFsm(&fsm);
    int event = EVENT1; 
    //小明的一天,周而复始的一天又一天,进行着相同的活动
    while (1)
    {
        printf("event %d is coming...\n", event);
        FSM_EventHandle(&fsm, event);
        printf("fsm current state %d\n", fsm.curState);
        test(&event); 
        sleep(1);  //休眠1秒,方便观察
    }

    return 0;
}

看一看该状态机跑起来的状态转移情况:

上面的图可以看出,当且仅当在指定的状态下来了指定的事件才会发生函数的执行以及状态的转移,否则不会发生状态的跳转。这种机制使得这个状态机不停地自动运转,有条不絮地完成任务。

与前两种方法相比,使用函数指针实现FSM能很好用于大规模的切换流程,只要我们实现搭好了FSM框架,以后进行扩展就很简单了(只要在状态表里加一行来写入新的状态处理就可以了)。

需要FSM完整代码的童鞋请访问我的github


# linux  # 小明  # 跳转  # 都是  # 实现了  # 有什么  # 使用了  # 都有  # 很好  # 是一种  # 自然语言 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: PHP 实现电台节目表的智能时间匹配与今日/明日轮播逻辑  Laravel怎么清理缓存_Laravel optimize clear命令详解  Laravel请求验证怎么写_Laravel Validator自定义表单验证规则教程  如何用低价快速搭建高质量网站?  零服务器AI建站解决方案:快速部署与云端平台低成本实践  浏览器如何快速切换搜索引擎_在地址栏使用不同搜索引擎【搜索】  香港服务器建站指南:外贸独立站搭建与跨境电商配置流程  Laravel Pest测试框架怎么用_从PHPUnit转向Pest的Laravel测试教程  如何利用DOS批处理实现定时关机操作详解  Laravel API资源类怎么用_Laravel API Resource数据转换  Laravel怎么为数据库表字段添加索引以优化查询  Win11怎样安装网易有道词典_Win11安装词典教程【步骤】  js实现点击每个li节点,都弹出其文本值及修改  如何在自有机房高效搭建专业网站?  Laravel项目如何进行性能优化_Laravel应用性能分析与优化技巧大全  PHP的CURL方法curl_setopt()函数案例介绍(抓取网页,POST数据)  西安市网站制作公司,哪个相亲网站比较好?西安比较好的相亲网站?  JS实现鼠标移上去显示图片或微信二维码  Laravel任务队列怎么用_Laravel Queues异步处理任务提升应用性能  佐糖AI抠图怎样调整抠图精度_佐糖AI精度调整与放大细化操作【攻略】  网站建设保证美观性,需要考虑的几点问题!  原生JS实现图片轮播切换效果  如何注册花生壳免费域名并搭建个人网站?  Laravel怎么发送邮件_Laravel Mail类SMTP配置教程  香港代理服务器配置指南:高匿IP选择、跨境加速与SEO优化技巧  Laravel怎么判断请求类型_Laravel Request isMethod用法  弹幕视频网站制作教程下载,弹幕视频网站是什么意思?  Laravel Blade模板引擎语法_Laravel Blade布局继承用法  Laravel如何处理CORS跨域问题_Laravel项目CORS配置与解决方案  Angular 表单中正确绑定输入值以确保提交与验证正常工作  如何快速配置高效服务器建站软件?  如何在景安服务器上快速搭建个人网站?  韩国服务器如何优化跨境访问实现高效连接?  安克发布新款氮化镓充电宝:体积缩小 30%,支持 200W 输出  JavaScript实现Fly Bird小游戏  Win11怎么开启自动HDR画质_Windows11显示设置HDR选项  如何快速上传建站程序避免常见错误?  猪八戒网站制作视频,开发一个猪八戒网站,大约需要多少?或者自己请程序员,需要什么程序员,多少程序员能完成?  laravel怎么配置和使用PHP-FPM来优化性能_laravel PHP-FPM配置与性能优化方法  夸克浏览器网页跳转延迟怎么办 夸克浏览器跳转优化  Edge浏览器怎么启用睡眠标签页_节省电脑内存占用优化技巧  如何在VPS电脑上快速搭建网站?  敲碗10年!Mac系列传将迎来「触控与联网」双革新  Laravel如何发送邮件和通知_Laravel邮件与通知系统发送步骤  公司网站制作需要多少钱,找人做公司网站需要多少钱?  如何快速登录WAP自助建站平台?  Laravel怎么实现验证码功能_Laravel集成验证码库防止机器人注册  如何快速搭建高效WAP手机网站吸引移动用户?  Edge浏览器提示“由你的组织管理”怎么解决_去除浏览器托管提示【修复】  怎么用AI帮你设计一套个性化的手机App图标?