一个协议,常常伴随几个,甚至十几个状态机,如何有效实现这些协议状态机是完成协议开发的基础;且协议状态机往往比较复杂,是协议的核心部分,初期实现不好,后期维护成本非常高,且容易修改引入,所以如何实现一个高效的状态机尤为重要。
一、状态机分析、设计
可以从以下4个方面进行:
1、各个状态的含义:一般分为初始态、接入态、正常态、终结态,802.3、802.11等标准上千个协议,而协议状态机也是大同小异,均可以应用这四个状态进行套接,例如我们最熟悉的TCP协议三次握手。
2、输入条件:除初始态外,各个状态均是由其他状态加上一定的输入条件切换而来。我们设计的状态机输入条件不易过多,可以参考标准协议状态机的输入条件设计,标准协议状态机的输入条件一般很简单,均不超过3个。如果设计的状态机有较多的输入条件,则状态机将变得复杂,状态切换不稳定,程序运行异常情况较多,后期维护成本非常高,所以但我们发现我们的状态机输入条件较多时(有的是后期修改bug临时添加的),则需要重新审视我们的状态机是否合理了,是否是因为修改bug采用了不合理的方案,或者需要将这个庞大的状态机进一步拆分。
3、输出结果:同输入条件,较多的输出同样导致程序复杂,控制输出结果不超过3个为宜。
4、状态切换:当前状态 + 输入条件 = 下一次状态 + 输出结果;状态切换不易太多,例如4个状态,最糟糕的是两两切换,可能的切换路径达到6次,此时我们的设计尽可能减少切换次数,降低状态机复杂度。
二、状态机编码实现
成功完成状态机分析、设计后,如何高效完成状态机编码。不建议采用switch case、if esle来实现,这样将导致成功设计的状态机程序复杂,可读性、可维护性较低,即不满足编程的SMART原则。
建议采用状态机驱动表机制,模型如下:
1、首先定义各个状态的处理函数指针:
typedef void (*fsmProc)(int *, char *) /* 状态机核心处理函数指针 */
如果状态机处理流程较复杂,还可以增加进入该状态的预处理和退出该状态的后处理
typedef void (*fsmPrevProc)(int *, char *) /* 进入状态预处理函数指针 */
typedef void (*fsmAftProc)(int *, char *) /* 退出状态后处理函数指针 */
2、定义状态机驱动表元素
typedef struct fsmproc{
fsmStt;
fsmProc;
fsmPrevProc;
fsmAftProc
}FSM_PROC
3、定义状态机驱动表
FSM_PROC fsmProcTbl[4] = {
{ fsmStt1, fsmStt1Proc, NULL, NULL },
{ fsmStt2, fsmStt2Proc, fsmStt2PrevProc, fsmStt2AftProc },
{ fsmStt3, fsmStt3Proc, fsmStt3PrevProc, fsmStt3AftProc },
{ fsmStt4, fsmStt4Proc, NULL, NULL },
}
部分较简单状态,例如初始态、终结态等,不需要预处理,后处理流程,可以设置成NULL
4、处理
状态机对外只需要提供一个fsmEntry接口,在需要触发状态切换的流程中调用该接口,在fsmEntry中调用对应接口即可:
if (fsmPrecTbl[fsmStt].fsmPrevProc)
{
ret |= fsmProcTbl[fsmStt].fsmPrevProc(a, b);
}
后记:再简单的程序都有出现bug的可能,不用说核心状态机处理流程,所以说在状态机增加可维护性流程非常重要。
在fsmEntry状态机中增加FSM_MTB,记录状态机运行轨迹,包括状态机状态、输入条件、输出结果等相关变量,可以方便出现问题时现场快速查询,快速定位。