状态机的状态迁移表写法
概述
VOIP服务器中,对于一通呼叫的管理一定会涉及到呼叫状态的变化,包括初始化、呼叫发起、振铃、接通、结束等各种状态。
呼叫业务流程为了管理呼叫状态的变化,就要用到有限状态机这一概念。
最简单的状态机实现,就是if-else或者switch分支方法。
当状态机的状态变化比较简单明了时,条件分支的写法就很好用,但是在条件和状态越来越复杂的情况下,就需要用到状态迁移表的写法,对于整体的状态变迁更容易扩展、维护和理解。
本文根据一个实际的呼叫业务需求,使用状态迁移表实现状态机的业务逻辑。
环境
centos:CentOS release 7.0 (Final)或以上版本
GCC:4.8.5
需求
原始需求:使用freeswitch的ESL接口实现双呼功能。
双呼功能,就是先对A号码发起呼叫,当A号码answer应答之后,再对B号码发起呼叫,并将A和B俩路呼叫bridge桥接起来,实现A和B的通话功能。
功能列表:
对外提供双呼接口,供第三方调用
内部使用FS的ESL接口,实现双呼业务逻辑
业务逻辑
状态机的基本业务逻辑如图
状态迁移表的逻辑如图
代码实现
源代码主要部分。
//呼叫状态
typedef enum ESL_CHANNEL_STATE
{
STATE_INIT = 0,
STATE_DIAL_A,
STATE_A_INVITING,
STATE_A_ANSWER,
STATE_WAIT_B_CALL_REQ,
STATE_DIAL_B,
STATE_B_INVITING,
STATE_B_ANSWER,
STATE_B_EXECUTE,
STATE_BRIDGE,
STATE_TALK,
STATE_HANGUP,
STATE_ERROR,
} CHANNEL_STATE;
//呼叫事件
typedef enum EN_IPC_HEADER_TYPE
{
ESL_CALL_REQ = 0, //发起呼叫请求
ESL_DIALACALL_REQ = 1, //发起呼叫ACall请求
ESL_DIALBCALL_REQ = 2, //发起呼叫BCall请求
ESL_RECORDCALL_REQ = 3, //发起录音请求
ESL_BRIDGECALL_REQ = 4, //发起bridge请求
ESL_CREAT_RESP = 5, //发起呼叫的响应
ESL_ANSWER_RESP = 6, //应答的事件
ESL_EXECUTE_RESP = 7, //命令执行响应
ESL_BRIDGECALL_RESP = 8, //发起bridge的响应
ESL_HANGUP_RESP = 9, //挂机的事件
ESL_CALL_NOTIFY_INVITE = 10, //INVITE通知
ESL_CALL_NOTIFY_ESTABLISH = 11, //ESTABLISH通知
ESL_HANGUP_REQ = 12, //发起挂机请求
ESL_HANGUP_NOTIFY = 13, //HANGUP通知
REST_TP_TS_SK_MSG= 14, // REST TP--->REST TS,传输层发给事务层的socket消息
REST_TP_CONN_MSG = 15, // REST TP新链接
REST_TP_DISCONN_MSG = 16, // REST TP链接中断,发送者transport的epoll delete sk并close sk
REST_TS_SEND_MSG = 17, // REST TS发送消息给REST SERVER
REST_TS_CLOSE_LINK = 18, // REST TS主动关闭链接,通知transport的epoll delete sk并close sk
REST_TS_BUFFER_MSG = 19, // 通知ts缓冲消息
REST_TS_RESEND_MSG = 20, // 通知tp,该数据为重发的消息
SYS_MSG_CHANGE_LOG_LEVEL = 21, // 外部应用通知CB更改日志级别
PROCESS_End = 22, // CB进程退出
ESL_RECODESTOP_RESP = 23, //录音结束响应
ESL_CANCEL_CB_REQ = 24, // 取消回拨
ESL_SCHED_HANDUP = 25, // 定时结束通话
ESL_RAS_HANDUP = 26, //隐私自定事件ESL_RAS_HANDUP
ESL_AS_IVR_HANGUP = 27, //中原自定义事件ESL_AS_IVR_HANGUP
ESL_VOICE_NOTICE_REQ = 28,
ESL_VOICE_NOTICE_HANGUP = 29,
ESL_RAS_DC_HANGUP = 30,
ESL_RAS_RC_HANGUP = 31,
ESL_VOICE_VERTIFY_REQ = 32,
ESL_VOICE_VERTIFY_HANGUP = 33,
FS_HEARTBEAT = 34, //FS 心跳
FS_DISCONNECT =35, //FS 断连
FS_RECONNECT = 36, //FS 重连
ESL_PLAY_VOICE_REQ = 39, //放音请求
ESL_VOICE_PLAYBACK_STOP = 40 //放音结束
} IPC_HEADER_TYPE;
//状态转移表定义
typedef struct ST_STATE_TRANSFER
{
CHANNEL_STATE curstate;
IPC_HEADER_TYPE eventtype;
CHANNEL_STATE nextstate;
} STATE_TRANSFER;
//全局状态转移表
STATE_TRANSFER g_stateTransferTable[]={
{STATE_INIT, ESL_CALL_REQ, STATE_DIAL_A},
{STATE_DIAL_A, ESL_CREAT_RESP, STATE_A_INVITING},
{STATE_A_INVITING, ESL_ANSWER_RESP, STATE_A_ANSWER},
{STATE_DIAL_B, ESL_CREAT_RESP, STATE_B_INVITING},
{STATE_B_INVITING, ESL_BRIDGECALL_RESP,STATE_BRIDGE},
{STATE_B_INVITING, ESL_ANSWER_RESP, STATE_B_ANSWER},
{STATE_BRIDGE, ESL_ANSWER_RESP, STATE_B_ANSWER},
{STATE_B_ANSWER, ESL_BRIDGECALL_RESP,STATE_BRIDGE},
{STATE_WAIT_B_CALL_REQ, ESL_DIALBCALL_REQ, STATE_DIAL_B},
{STATE_WAIT_B_CALL_REQ, ESL_HANGUP_RESP, STATE_HANGUP},
//{STATE_B_INVITING, ESL_EXECUTE_RESP, STATE_B_EXECUTE},
//{STATE_B_ANSWER, ESL_EXECUTE_RESP, STATE_B_EXECUTE},
//{STATE_B_EXECUTE, ESL_ANSWER_RESP, STATE_B_ANSWER},
//{STATE_BRIDGE, ESL_BRIDGECALL_RESP,STATE_TALK},
{STATE_DIAL_A, ESL_HANGUP_RESP, STATE_HANGUP},
{STATE_A_INVITING, ESL_HANGUP_RESP, STATE_HANGUP},
{STATE_A_ANSWER, ESL_HANGUP_RESP, STATE_HANGUP},
{STATE_DIAL_B, ESL_HANGUP_RESP, STATE_HANGUP},
{STATE_B_INVITING, ESL_HANGUP_RESP, STATE_HANGUP},
{STATE_B_ANSWER, ESL_HANGUP_RESP, STATE_HANGUP},
{STATE_B_EXECUTE, ESL_HANGUP_RESP, STATE_HANGUP},
{STATE_BRIDGE, ESL_HANGUP_RESP, STATE_HANGUP},
{STATE_TALK, ESL_HANGUP_RESP, STATE_HANGUP}
};
//状态转移回调函数指针定义
typedef void (*PFUNToState)(CALL_INFO *pcallinfo);
//状态转移回调函数映射表,表中函数指针顺序与CHANNEL_STATE的顺序要保持一致
PFUNToState g_pfunToState[]={
CEslStateMachine::PFUNToInit,
CEslStateMachine::PFUNToDIAL_A,
CEslStateMachine::PFUNToA_INVITING,
CEslStateMachine::PFUNToA_ANSWER,
CEslStateMachine::PFUNToWait_B_CaLL_REQ,
CEslStateMachine::PFUNToDIAL_B,
CEslStateMachine::PFUNToB_INVITING,
CEslStateMachine::PFUNToB_ANSWER,
CEslStateMachine::PFUNToB_EXECUTE,
CEslStateMachine::PFUNToBRIDGE,
CEslStateMachine::PFUNToTALK,
CEslStateMachine::PFUNToHANGUP,
CEslStateMachine::PFUNToERROR
};
//状态机事件分发逻辑实现
void CEslStateMachine::dispatch(CALL_INFO *pcallinfo, int event)
{
PrintLog(DEBUG, "%s, pcallinfo=%8p", __FUNCTION__, pcallinfo);
if(NULL == pcallinfo)
{
return;
}
int curstate = pcallinfo->curstate;
for(unsigned int i = 0; i < sizeof(g_stateTransferTable)/sizeof(STATE_TRANSFER); i++)
{
if(curstate == g_stateTransferTable[i].curstate && event == g_stateTransferTable[i].eventtype)
{
g_pfunToState[g_stateTransferTable[i].nextstate](pcallinfo);
break;
}
}
}
//INIT状态回调函数实现
void CEslStateMachine::PFUNToInit(CALL_INFO *pcallinfo)
{
}
//DIAL_A状态回调函数实现
void CEslStateMachine::PFUNToDIAL_A(CALL_INFO *pcallinfo)
{
pcallinfo->curstate = STATE_DIAL_A;
//send msg to transport
if(NULL != m_pStateMachine)
{
m_pStateMachine->SendMsgDailAReq(pcallinfo);
}
}
…
总结
本文针对状态机的迁移表写法进行了介绍,读者可以根据自己的业务需求来选择实现方案。
学习最好的办法还是亲自动手试试,just do it。
空空如常
求真得真