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

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

处理程序语言或者自然语言的 tokenizer,自底向上解析语法的parser,

各种通信协议发送方和接受方传递数据对消息处理,游戏AI等都有应用场景。

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

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

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

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


  1. //比如我们定义了小明一天的状态如下
  2. enum
  3. {
  4. GET_UP,
  5. GO_TO_SCHOOL,
  6. HAVE_LUNCH,
  7. GO_HOME,
  8. DO_HOMEWORK,
  9. SLEEP,
  10. };
  11. int main()
  12. {
  13. int state = GET_UP;
  14. //小明的一天
  15. while (1)
  16. {
  17. if (state == GET_UP)
  18. {
  19. GetUp(); //具体调用的函数
  20. state = GO_TO_SCHOOL; //状态的转移
  21. }
  22. else if (state == GO_TO_SCHOOL)
  23. {
  24. Go2School();
  25. state = HAVE_LUNCH;
  26. }
  27. else if (state == HAVE_LUNCH)
  28. {
  29. HaveLunch();
  30. }
  31. ...
  32. else if (state == SLEEP)
  33. {
  34. Go2Bed();
  35. state = GET_UP;
  36. }
  37. }
  38. return 0;
  39. }

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

二、使用switch实现FSM

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

  1. int main()
  2. {
  3. int state = GET_UP;
  4. //小明的一天
  5. while (1)
  6. {
  7. switch(state)
  8. {
  9. case GET_UP:
  10. GetUp(); //具体调用的函数
  11. state = GO_TO_SCHOOL; //状态的转移
  12. break;
  13. case GO_TO_SCHOOL:
  14. Go2School();
  15. state = HAVE_LUNCH;
  16. break;
  17. case HAVE_LUNCH:
  18. HaveLunch();
  19. state = GO_HOME;
  20. break;
  21. ...
  22. default:
  23. break;
  24. }
  25. }
  26. return 0;
  27. }

三、使用函数指针实现FSM

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

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

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

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

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

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

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

  1. //比如我们定义了小明一天的状态如下
  2. enum
  3. {
  4. GET_UP,
  5. GO_TO_SCHOOL,
  6. HAVE_LUNCH,
  7. DO_HOMEWORK,
  8. SLEEP,
  9. };

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

  1. enum
  2. {
  3. EVENT1 = 1,
  4. EVENT2,
  5. EVENT3,
  6. };

定义状态表的数据结构

  1. typedef struct FsmTable_s
  2. {
  3. int event; //事件
  4. int CurState; //当前状态
  5. void (*eventActFun)(); //函数指针
  6. int NextState; //下一个状态
  7. }FsmTable_t;

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

  1. FsmTable_t XiaoMingTable[] =
  2. {
  3. //{到来的事件,当前的状态,将要要执行的函数,下一个状态}
  4. { EVENT2, SLEEP, GetUp, GET_UP },
  5. { EVENT1, GET_UP, Go2School, GO_TO_SCHOOL },
  6. { EVENT2, GO_TO_SCHOOL, HaveLunch, HAVE_LUNCH },
  7. { EVENT3, HAVE_LUNCH, DoHomework, DO_HOMEWORK },
  8. { EVENT1, DO_HOMEWORK, Go2Bed, SLEEP },
  9. //add your codes here
  10. };

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

  1. /*状态机注册*/
  2. void FSM_Regist(FSM_t* pFsm, FsmTable_t* pTable)
  3. {
  4. pFsm->FsmTable = pTable;
  5. }
  6. /*状态迁移*/
  7. void FSM_StateTransfer(FSM_t* pFsm, int state)
  8. {
  9. pFsm->curState = state;
  10. }
  11. /*事件处理*/
  12. void FSM_EventHandle(FSM_t* pFsm, int event)
  13. {
  14. FsmTable_t* pActTable = pFsm->FsmTable;
  15. void (*eventActFun)() = NULL; //函数指针初始化为空
  16. int NextState;
  17. int CurState = pFsm->curState;
  18. int flag = 0; //标识是否满足条件
  19. int i;
  20. /*获取当前动作函数*/
  21. for (i = 0; i<g_max_num; i++)
  22. {
  23. //当且仅当当前状态下来个指定的事件,我才执行它
  24. if (event == pActTable[i].event && CurState == pActTable[i].CurState)
  25. {
  26. flag = 1;
  27. eventActFun = pActTable[i].eventActFun;
  28. NextState = pActTable[i].NextState;
  29. break;
  30. }
  31. }
  32. if (flag) //如果满足条件了
  33. {
  34. /*动作执行*/
  35. if (eventActFun)
  36. {
  37. eventActFun();
  38. }
  39. //跳转到下一个状态
  40. FSM_StateTransfer(pFsm, NextState);
  41. }
  42. else
  43. {
  44. // do nothing
  45. }
  46. }

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

  1. int main()
  2. {
  3. FSM_t fsm;
  4. InitFsm(&fsm);
  5. int event = EVENT1;
  6. //小明的一天,周而复始的一天又一天,进行着相同的活动
  7. while (1)
  8. {
  9. printf("event %d is coming...\n", event);
  10. FSM_EventHandle(&fsm, event);
  11. printf("fsm current state %d\n", fsm.curState);
  12. test(&event);
  13. sleep(1); //休眠1秒,方便观察
  14. }
  15. return 0;
  16. }

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

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

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

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

Linux编程之有限状态机FSM的理解与实现的更多相关文章

  1. Atitit. 有限状态机 fsm 状态模式

    Atitit. 有限状态机 fsm 状态模式 1. 有限状态机 1 2. "状态表"和"状态轮换表" 1 3. 有限状态机概念(状态(State)事件(Even ...

  2. 牛人整理分享的面试知识:操作系统、计算机网络、设计模式、Linux编程,数据结构总结 转载

    基础篇:操作系统.计算机网络.设计模式 一:操作系统 1. 进程的有哪几种状态,状态转换图,及导致转换的事件. 2. 进程与线程的区别. 3. 进程通信的几种方式. 4. 线程同步几种方式.(一定要会 ...

  3. 【转】牛人整理分享的面试知识:操作系统、计算机网络、设计模式、Linux编程,数据结构总结

    基础篇:操作系统.计算机网络.设计模式 一:操作系统 1. 进程的有哪几种状态,状态转换图,及导致转换的事件. 2. 进程与线程的区别. 3. 进程通信的几种方式. 4. 线程同步几种方式.(一定要会 ...

  4. Linux 编程中的API函数和系统调用的关系【转】

    转自:http://blog.chinaunix.net/uid-25968088-id-3426027.html 原文地址:Linux 编程中的API函数和系统调用的关系 作者:up哥小号 API: ...

  5. 面试知识:操作系统、计算机网络、设计模式、Linux编程,数据结构总结

    基础篇:操作系统.计算机网络.设计模式 一:操作系统 1. 进程的有哪几种状态,状态转换图,及导致转换的事件. 2. 进程与线程的区别. 3. 进程通信的几种方式. 4. 线程同步几种方式.(一定要会 ...

  6. Linux编程简介

    Linux编程可以分为Shell(如BASH.TCSH.GAWK.Perl.Tcl和Tk等)编程和高级语言(C语言,C++语言,java语言等)编程,Linux程序需要首先转化为低级机器语言即所谓的二 ...

  7. 有限状态机FSM(自动售报机Verilog实现)

    有限状态机FSM(自动售报机Verilog实现) FSM 状态机就是一种能够描述具有逻辑顺序和时序顺序事件的方法. 状态机有两大类:Mealy型和Moore型. Moore型状态机的输出只与当前状态有 ...

  8. 有限状态机FSM

    有限状态机(Finite-state machine)又称有限状态自动机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型.常用与:正则表达式引擎,编译器的词法和语法分析,游戏设计,网络 ...

  9. 掌握Linux编程的10个步骤

    Linux 编程经典书籍推荐 Denis 2008年10月17日 浏览:84168 成为一名精通 Linux 程序设计的高级程序员一直是不少朋友孜孜以求的目标.根据中华英才网统计数据,北京地区 Lin ...

随机推荐

  1. 玩转spring boot——ajax跨域

    前言  java语言在多数时,会作为一个后端语言,为前端的php,node.js等提供API接口.前端通过ajax请求去调用java的API服务.今天以node.js为例,介绍两种跨域方式:Cross ...

  2. 九思,OA协同九大设计要点

    伴随着产品线的丰富和客户数量的增加,我们发现烂尾项目也与日俱增,客户和OA公司之间的矛盾日益尖锐,一套好OA系统远非增加几个特色功能这么简单,套用孔子"君子有九思"的话,好的OA系 ...

  3. linux下vim编辑器使用

    VIM - Vi IMproved: vim是vi编辑器的升级版,是linux下标准的编辑器,具有程序编写能力,可以根据字体颜色辨别语法的正确性,方便程序的设计. 使用: # vim [OPTION] ...

  4. Kafka权威指南——broker的常用配置

    前面章节中的例子,用来作为单个节点的服务器示例是足够的,但是如果想要把它应用到生产环境,就远远不够了.在Kafka中有很多参数可以控制它的运行和工作.大部分的选项都可以忽略直接使用默认值就好,遇到一些 ...

  5. mysql中 date datetime time timestamp 的区别

    MySQL中关于时间的数据类型:它们分别是 date.datetime.time.timestamp.year date :"yyyy-mm-dd"  日期     1000-01 ...

  6. 浏览器如何生成URL

    点击页面中的链接,浏览器会根据源码中相对URL路径作不同的处理: (1)有协议名称,但没有域名信息 对于这种形式的URL,它的协议,路径,查询字符串和片段ID都以它自身为准,但域名信息的部分,以引用它 ...

  7. Java基础知识二次学习-- 第一章 java基础

    基础知识有时候感觉时间长似乎有点生疏,正好这几天有时间有机会,就决定重新做一轮二次学习,挑重避轻 回过头来重新整理基础知识,能收获到之前不少遗漏的,所以这一次就称作查漏补缺吧!废话不多说,开始! 第一 ...

  8. java集合(1)- 类底层数据结构分析

    Java 集合类图 参考:http://www.cnblogs.com/xwdreamer/archive/2012/05/30/2526822.html

  9. java虚拟机学习-深入理解JVM(1)

    1   Java技术与Java虚拟机 说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成: Java编程语言.Java类文件格式.Java虚拟机和Java应 ...

  10. Hibernate与Jpa的关系(2)

    [转自:http://blog.163.com/hero_213/blog/static/398912142010312024809/ ] 近年来ORM(Object-Relational Mappi ...