优秀的编程知识分享平台

网站首页 > 技术文章 正文

状态机的状态迁移表写法(状态迁移图设计方法)

nanyue 2024-07-29 01:29:25 技术文章 44 ℃



概述

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。


空空如常

求真得真

Tags:

最近发表
标签列表