状态机的状态迁移表写法

 

 

概述

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。

 


 

空空如常

求真得真

 

posted @ 2021-12-15 14:31  求真得真  阅读(410)  评论(0编辑  收藏  举报