freeswitch.Session的实现过程

本文详细介绍了Freeswitch中Session对象的创建过程,包括lua中通过freeswitch.Session()创建Session对象的四种方式,以及在C++层面的实现细节。接着讨论了呼叫字符串的解析步骤,主要由switch_event_create_brackets()完成,解析后的键值对存储在switch_event_t对象中。最后,探讨了回调函数的使用,当channel被应答时,如何触发预设的回调app并执行相应的操作。

一.创建Session

在lua中通过调用 freeswitch.Session()可以创建一个Session对象。按源码里的注释,他包括四个原型:
LUA::Session::Session()
LUA::Session::Session(char ,CoreSession )
LUA::Session::Session(char *)
LUA::Session::Session(switch_core_session_t *)
所以他有四个初始化的方式。

mod_lua_wrap.cpp这个文件主要是将C++的函数接口封装成lua接口,在这里面:

static swig_lua_class _wrap_class_LUA_Session = { "Session", &SWIGTYPE_p_LUA__Session,_wrap_new_Session, swig_delete_Session, swig_LUA_Session_methods, swig_LUA_Session_attributes, { "Session", swig_LUA_Session_cls_methods, swig_LUA_Session_cls_attributes, swig_LUA_Session_cls_constants }, swig_LUA_Session_bases, swig_LUA_Session_base_names };

创建了一个swig_lua_class

typedef struct swig_lua_class {
  const char    *name;
  swig_type_info   **type;
  lua_CFunction  constructor;
  void    (*destructor)(void *);
  swig_lua_method   *methods;
  swig_lua_attribute     *attributes;
  struct swig_lua_class **bases;
  const char **base_names;
} swig_lua_class;

这个是表示一个注册lua对象的结构体。其实就是通过注册一个个swig_lua_class,使得lua脚本可以想调用对象一样调用C++里面的对象的方法。

name 是在lua中的名字
constructor 可以理解为构造函数
destructor 自然是析构函数
methods 这个对象的方法
attributes这个对象的属性

所以构造函数是:_wrap_new_Session()也是在执行freeswitch.Session的时候会调用_wrap_new_Session()这个函数。

static int _wrap_new_Session(lua_State* L) {
  int argc;
  int argv[3]={
    1,2,3
  };

  argc = lua_gettop(L);
  if (argc == 0) {
    return _wrap_new_Session__SWIG_0(L);
  }
  if (argc == 1) {
    int _v;
    {
      void *ptr;
      if (SWIG_isptrtype(L,argv[0])==0 || SWIG_ConvertPtr(L,argv[0], (void **) &ptr, SWIGTYPE_p_switch_core_session_t, 0)) {
        _v = 0;
      } else {
        _v = 1;
      }
    }
    if (_v) {
      return _wrap_new_Session__SWIG_3(L);
    }
  }
  if (argc == 1) {
    int _v;
    {
      _v = SWIG_lua_isnilstring(L,argv[0]);
    }
    if (_v) {
      return _wrap_new_Session__SWIG_2(L);
    }
  }
  if (argc == 2) {
    int _v;
    {
      _v = SWIG_lua_isnilstring(L,argv[0]);
    }
    if (_v) {
      {
        void *ptr;
        if (SWIG_isptrtype(L,argv[1])==0 || SWIG_ConvertPtr(L,argv[1], (void **) &ptr, SWIGTYPE_p_CoreSession, 0)) {
          _v = 0;
        } else {
          _v = 1;
        }
      }
      if (_v) {
        return _wrap_new_Session__SWIG_1(L);
      }
    }
  }

  SWIG_Lua_pusherrstring(L,"Wrong arguments for overloaded function 'new_Session'\n"
    "  Possible C/C++ prototypes are:\n"
    "    LUA::Session::Session()\n"
    "    LUA::Session::Session(char *,CoreSession *)\n"
    "    LUA::Session::Session(char *)\n"
    "    LUA::Session::Session(switch_core_session_t *)\n");
  lua_error(L);return 0;
}

可以看到在_wrap_new_Session中主要是依据参数和版本情况调用不同的创建函数:
_wrap_new_Session__SWIG_0
_wrap_new_Session__SWIG_1
_wrap_new_Session__SWIG_2
_wrap_new_Session__SWIG_3

我们以_wrap_new_Session__SWIG_1为例分析它的实现

static int _wrap_new_Session__SWIG_1(lua_State* L) {
  int SWIG_arg = 0;
  char *arg1 = (char *) 0 ;
  CoreSession *arg2 = (CoreSession *) 0 ;
  LUA::Session *result = 0 ;

  SWIG_check_num_args("LUA::Session::Session",2,2)
  if(!SWIG_lua_isnilstring(L,1)) SWIG_fail_arg("LUA::Session::Session",1,"char *");
  if(!SWIG_isptrtype(L,2)) SWIG_fail_arg("LUA::Session::Session",2,"CoreSession *");
  arg1 = (char *)lua_tostring(L, 1);

  if (!SWIG_IsOK(SWIG_ConvertPtr(L,2,(void**)&arg2,SWIGTYPE_p_CoreSession,0))){
    SWIG_fail_ptr("new_Session",2,SWIGTYPE_p_CoreSession);
  }

  result = (LUA::Session *)new LUA::Session(arg1,arg2);
  SWIG_NewPointerObj(L,result,SWIGTYPE_p_LUA__Session,1); SWIG_arg++; 
  return SWIG_arg;

  if(0) SWIG_fail;

fail:
  lua_error(L);
  return SWIG_arg;
}

可以看到先检查参数的个数及其类型,必须是两个参数而且第一个是字符串型,第二个是指向CoreSession 的指针类型。
然后就开始干活了

result = (LUA::Session *)new LUA::Session(arg1,arg2);

在这里创建了一个c++层Session对象!!!
然后将这个新的Session对象压人栈顶,增加参数计数器,然后返回。

SWIG_NewPointerObj(L,result,SWIGTYPE_p_LUA__Session,1); SWIG_arg++; 

这里需要注意的是几个宏:
SWIG_NewPointerObj 创建一个lua层可以访问的指针对象,其实最终是在栈顶通过lua_newuserdata创建了swig_lua_userdata对象保存了指针,类型及owner的信息


/* pushes a new object into the lua stack */
SWIGRUNTIME void SWIG_Lua_NewPointerObj(lua_State* L,void* ptr,swig_type_info *type, int own)
{
  swig_lua_userdata* usr;
  if (!ptr){
    lua_pushnil(L);
    return;
  }
  usr=(swig_lua_userdata*)lua_newuserdata(L,sizeof(swig_lua_userdata));  /* get data */
  usr->ptr=ptr;  /* set the ptr */
  usr->type=type;
  usr->own=own;
#if (SWIG_LUA_TARGET != SWIG_LUA_FLAVOR_ELUAC)
  _SWIG_Lua_AddMetatable(L,type); /* add metatable */
#endif
}

最后这里补充一下的是_wrap_class_LUA_Session进一步被封装在类型为swig_type_info的_swigt__p_LUA__Session 这个结构体中:

static swig_type_info _swigt__p_LUA__Session = {"_p_LUA__Session", "LUA::Session *", 0, 0, (void*)&_wrap_class_LUA_Session, 0};

然后_swigt__p_LUA__Session 作为swig_type_initial数组的一个元素。
显然swig_type_initial这一坨东西就是在mod_lua初始化的时候会注册给lua脚本使用的各个函数。

在回头看下new LUA::Session()这个创建对象的过程。

namespace LUA {
    class Session:public CoreSession {

LUA::Session继承CoreSession,所以主要调用的是CoreSession的构造函数。

SWITCH_DECLARE_CONSTRUCTOR CoreSession::CoreSession(char *nuuid, CoreSession *a_leg)
{
    switch_channel_t *other_channel = NULL;

    init_vars();

    if (a_leg && a_leg->session) {
        other_channel = switch_core_session_get_channel(a_leg->session);
    }

    if (!strchr(nuuid, '/') && (session = switch_core_session_force_locate(nuuid))) {
        uuid = strdup(nuuid);
        channel = switch_core_session_get_channel(session);
        allocated = 1;
    } else {
        cause = SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER;
        if (switch_ivr_originate(a_leg ? a_leg->session : NULL, &session, &cause, nuuid, 60, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL) 
            == SWITCH_STATUS_SUCCESS) {
            channel = switch_core_session_get_channel(session);
            allocated = 1;
            switch_set_flag(this, S_HUP);
            uuid = strdup(switch_core_session_get_uuid(session));
            switch_channel_set_state(switch_core_session_get_channel(session), CS_SOFT_EXECUTE);
            switch_channel_wait_for_state(channel, other_channel, CS_SOFT_EXECUTE);
        }
    }
}

所以主要是调用switch_ivr_originate()。这个函数应该是非常熟悉了。

二.解析呼叫字符串

我们再分析一下呼叫字符串的解析过程:

while (*data == '{') {
        char *parsed = NULL;

        if (switch_event_create_brackets(data, '{', '}', ',', &var_event, &parsed, SWITCH_FALSE) != SWITCH_STATUS_SUCCESS || !parsed) {
            switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Parse Error!\n");
            switch_goto_status(SWITCH_STATUS_GENERR, done);
        }

        data = parsed;
    }

以左大括号”{“开始,”}”结束,中间支持”,”号隔开。实际的解析工作是switch_event_create_brackets()完成的。

SWITCH_DECLARE(switch_status_t) switch_event_create_brackets(char *data, char a, char b, char c, switch_event_t **event, char **new_data, switch_bool_t dup)
{
    char *vdata, *vdatap = NULL;
    char *end, *check_a, *check_b;
    switch_event_t *e = *event;
    char *var_array[1024] = { 0 };
    int var_count = 0;
    char *next = NULL, *vnext = NULL;

    if (dup) {
        vdatap = strdup(data);
        vdata = vdatap;
    } else {
        vdata = data;
    }

    end = switch_find_end_paren(vdata, a, b);

    check_a = end;

    while (check_a && (check_b = switch_strchr_strict(check_a, a, " "))) {
        if ((check_b = switch_find_end_paren(check_b, a, b))) {
            check_a = check_b;
        }
    }

    if (check_a) end = check_a;

    if (end) {
        next = end;
        vdata++;
        *end++ = '\0';
    } else {
        if (dup) {
            free(vdatap);
        }
        return SWITCH_STATUS_FALSE;
    }

    if (!e) {
        switch_event_create_plain(&e, SWITCH_EVENT_CHANNEL_DATA);
    }


    for (;;) {
        if (next) {
            char *pnext;

            *next++ = '\0';

            if ((pnext = switch_strchr_strict(next, a, " "))) {
                next = pnext + 1;
            }

            vnext = switch_find_end_paren(next, a, b);
            next = NULL;
        }


        if (vdata) {
            if (*vdata == '^' && *(vdata + 1) == '^') {
                vdata += 2;
                c = *vdata++;
            }
        }

        if ((var_count = switch_separate_string(vdata, c, var_array, (sizeof(var_array) / sizeof(var_array[0]))))) {
            int x = 0;
            for (x = 0; x < var_count; x++) {
                char *inner_var_array[2] = { 0 };
                int inner_var_count;

                if ((inner_var_count = switch_separate_string(var_array[x], '=',
                                                              inner_var_array, (sizeof(inner_var_array) / sizeof(inner_var_array[0])))) == 2) {
                    switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Parsing variable [%s]=[%s]\n", inner_var_array[0], inner_var_array[1]);
                    switch_event_add_header_string(e, SWITCH_STACK_BOTTOM, inner_var_array[0], inner_var_array[1]);
                }
            }
        }

        if (vnext) {
            vdata = vnext;
            vnext = NULL;
        } else {
            break;
        }

    }

    *event = e;

    if (dup) {
        *new_data = strdup(end);
        free(vdatap);
    } else {
        *new_data = end;
    }

    return SWITCH_STATUS_SUCCESS;

}

switch_find_end_paren得到的是下一个”}”的位置。那这样子就找到了一个个由”{ ……}”组成的子字符串,注意这里会把末尾的字符赋值为:’\0’,这样子,实际上字符串已经被做了分割。
对于子字符串,如果是以^^开头的那分隔符是下一个字符,可自定义,不然就用默认分隔符“,”。于是又得到了一个个被分割的子字符串。
对于得到的新子串,默认情况,以“=”号分割得到一对对的键值对。然后保存到switch_event_t对象中的头部文件headString里面。

三.回调函数的使用

接下来我们再看一下设置的这些属性/回调app/回调api是怎么触发。我们以execute_on_answer为例分析一下(各种事件类型在switch_types.h中定义):

#define SWITCH_CHANNEL_EXECUTE_ON_ANSWER_VARIABLE "execute_on_answer"

一个channel被应答后,会调用switch_channel_perform_mark_answered(),在这个函数里面有怎么一段:

switch_channel_execute_on(channel, SWITCH_CHANNEL_EXECUTE_ON_ANSWER_VARIABLE);

这里就是去检查之前设置好的回调app并调用他(如果想回调api的时候会用:switch_channel_api_on(switch_channel_t *channel, const char *variable_prefix)。我们再看下switch_channel_execute_on的实现。


SWITCH_DECLARE(switch_status_t) switch_channel_execute_on(switch_channel_t *channel, const char *variable_prefix)
{
    switch_event_header_t *hp;
    switch_event_t *event, *cevent;
    int x = 0;

    switch_core_get_variables(&event);
    switch_channel_get_variables(channel, &cevent);
    switch_event_merge(event, cevent);

    for (hp = event->headers; hp; hp = hp->next) {
        char *var = hp->name;
        char *val = hp->value;

        if (!strncasecmp(var, variable_prefix, strlen(variable_prefix))) {
            if (hp->idx) {
                int i;
                for (i = 0; i < hp->idx; i++) {
                    x++;
                    do_execute_on(channel, hp->array[i]);                   
                }
            } else {
                x++;
                do_execute_on(channel, val);
            }
        }
    }

    switch_event_destroy(&event);
    switch_event_destroy(&cevent);

    return x ? SWITCH_STATUS_SUCCESS : SWITCH_STATUS_FALSE;
}

可以看到拿到event之后就会遍历event的头部headers(这个跟刚刚解析呼叫字符串,然后结果保存到event里面去就对应上了),如果头部的名字是以variable_prefix开头的那就找到了,顺理成章的,下面就调用:do_execute_on()去执行上面预置好的方法。

static void do_execute_on(switch_channel_t *channel, const char *variable)
{
    char *arg = NULL;
    char *p;
    int bg = 0;
    char *app;

    app = switch_core_session_strdup(channel->session, variable);

    for(p = app; p && *p; p++) {
        if (*p == ' ' || (*p == ':' && (*(p+1) != ':'))) {
            *p++ = '\0';
            arg = p;
            break;
        } else if (*p == ':' && (*(p+1) == ':')) {
            bg++;
            break;
        }
    }

    if (!strncasecmp(app, "perl", 4)) {
        bg++;
    }

    if (bg) {
        switch_core_session_execute_application_async(channel->session, app, arg);
    } else {
        switch_core_session_execute_application(channel->session, app, arg);
    }
}

这里解析出app和arg然后调用switch_core_session_execute_application()在通道上执行对应的app。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值