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 skREST_TS_SEND_MSG = 17, // REST TS发送消息给REST SERVERREST_TS_CLOSE_LINK = 18, // REST TS主动关闭链接,通知transport的epoll delete sk并close skREST_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_HANDUPESL_AS_IVR_HANGUP = 27, //中原自定义事件ESL_AS_IVR_HANGUPESL_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 transportif(NULL != m_pStateMachine){m_pStateMachine->SendMsgDailAReq(pcallinfo);}}…
本文针对状态机的迁移表写法进行了介绍,读者可以根据自己的业务需求来选择实现方案。
学习最好的办法还是亲自动手试试,just do it。
空空如常
求真得真
本文发布于:2024-02-02 05:57:52,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170682467341817.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |