通道变量存储与传递
从代码的注释可以看出,freeswitch使用了一种特殊的形式来存储呼叫参数和通道变量,那就是存到event中。
目前来看,应该是存储到类型为SWITCH_EVENT_CHANNEL_DATA的事件中。
/ Some channel are created from an originating channel and some aren’t so not all outgoing calls have a way to get params so we will normalize dialstring params and channel variables (when there is an originator) into an event that we
will use as a pseudo hash to consult for params as needed. / if (ovars) { var_event = ovars; } else { if (switch_event_create_plain(&var_event, SWITCH_EVENT_CHANNEL_DATA) != SWITCH_STATUS_SUCCESS) { abort(); } }
数据结构
EventHeader
struct switch_event_header {/*! the header name */char *name;/*! the header value */char *value;/*! array space */char **array;/*! array index */int idx;/*! hash of the header name */unsigned long hash;struct switch_event_header *next;};
可以看到,header中含有典型的key-val形式的数据,比较适合通道变量的存储。
Event
event数据结构如下:
struct switch_event {/*! the event id (descriptor) */switch_event_types_t event_id;/*! the priority of the event */switch_priority_t priority;/*! the owner of the event */char *owner;/*! the subclass of the event */char *subclass_name;/*! the event headers */switch_event_header_t *headers;/*! the event headers tail pointer */switch_event_header_t *last_header;/*! the body of the event */char *body;/*! user data from the subclass provider */void *bind_user_data;/*! user data from the event sender */void *event_user_data;/*! unique key */unsigned long key;struct switch_event *next;int flags;};
主要使用headers来存储各个通道变量。
用于存储通道变量的事件类型比较特殊,是SWITCH_EVENT_CHANNEL_DATA。
简单关系图
基本操作
创建事件(特殊事件,只用于存储各种变量)
创建一个特殊的事件,类型是SWITCH_EVENT_CHANNEL_DATA
//文件:switch_ivr_originate.c -> switch_ivr_originate()函数if (switch_event_create_plain(&var_event, SWITCH_EVENT_CHANNEL_DATA) != SWITCH_STATUS_SUCCESS) {abort();}
switch_event_create_plain函数函数内部创建的事件类型是SWITCH_EVENT_CLONE。
创建完成后,才重新改为SWITCH_EVENT_CHANNEL_DATA类型。
如下:
//文件:switch_event.hstatic inline switch_status_t switch_event_create_plain(switch_event_t **event, switch_event_types_t event_id){switch_status_t status = switch_event_create(event, SWITCH_EVENT_CLONE);if (status == SWITCH_STATUS_SUCCESS) {//创建事件成功后,将事件id由SWITCH_EVENT_CLONE修改为SWITCH_EVENT_CHANNEL_DATA(*event)->event_id = event_id;//设置事件的header不允许重复if (event_id == SWITCH_EVENT_REQUEST_PARAMS || event_id == SWITCH_EVENT_CHANNEL_DATA) {(*event)->flags |= EF_UNIQ_HEADERS;}}return status;}
再次往下跟踪switch_event_create:
switch_event_create实际是两层的宏,具体如下:
//文件: switch_event.h#define switch_event_create(event, id) switch_event_create_subclass(event, id, SWITCH_EVENT_SUBCLASS_ANY)#define switch_event_create_subclass(_e, _eid, _sn) switch_event_create_subclass_detailed(__FILE__, (const char * )__SWITCH_FUNC__, __LINE__, _e, _eid, _sn)
实际解析到最后的时候,switch_event_create_subclass_detailed大部分参数都是空的,如
FILE 指代的是调用者所在文件 SWITCHFUNC 指代的是调用的函数 _LINE 指代的是调用函数的行号 _sn 子事件名称, 这里用的一个宏SWITCH_EVENT_SUBCLASS_ANY,实际解析到最后也是0 _e的类型是SWITCH_EVENT_CLONE
上面这些大部分都是空的值,因为不是真正的事件,由宏屏蔽后,就可以少传这些参数。
下面再来到具体真正创建事件的地方(去掉了部分代码):
SWITCH_DECLARE(switch_status_t) switch_event_create_subclass_detailed(const char *file, const char *func, int line,switch_event_t **event, switch_event_types_t event_id, const char *subclass_name){...*event = NULL;//分配事件内存空间,并进行初始化*event = ALLOC(sizeof(switch_event_t));switch_assert(*event);memset(*event, 0, sizeof(switch_event_t));if (event_id == SWITCH_EVENT_REQUEST_PARAMS || event_id == SWITCH_EVENT_CHANNEL_DATA || event_id == SWITCH_EVENT_MESSAGE) {(*event)->flags |= EF_UNIQ_HEADERS;}//这个才是正正经经的事件代码,需要初始化一些基本的的事件头,如Event-Name、Core-UUID、Event-Date-Local、Event-Calling-File等if (event_id != SWITCH_EVENT_CLONE) {(*event)->event_id = event_id;switch_event_prep_for_delivery_detailed(file, func, line, *event);}//发现是custom xxx事件的话,还得把子事件的名字写进来if (subclass_name) {(*event)->subclass_name = DUP(subclass_name);switch_event_add_header_string(*event, SWITCH_STACK_BOTTOM, "Event-Subclass", subclass_name);}return SWITCH_STATUS_SUCCESS;}
从代码中可以看出,我们创建的特殊事件(内部是SWITCH_EVENT_CLONE),是没有调用switch_event_prep_for_delivery_detailed进行普通事件的初始化的。
因为它本身就不是用来进行事件流转的,是用来存储数据的。
可以参考下这个 链接,讲的比较详细。
设置通道变量
switch_channel_set_variable
调用方式:switch_channel_set_variable(caller_channel, "bypass_media_after_bridge", "true");
该函数为一个宏,具体如下:
//文件:switch_channel.hSWITCH_DECLARE(switch_status_t) switch_channel_set_variable_var_check(switch_channel_t *channel,const char *varname, const char *value, switch_bool_t var_check);#define switch_channel_set_variable(_channel, _var, _val) switch_channel_set_variable_var_check(_channel, _var, _val, SWITCH_TRUE)
具体调用的是switch_channel_set_variable_var_check,具体代码如下:
主要是增加一个变量检查,然后调用switch_event_add_header_string来真正的添加变量。
//文件:switch_channel.cSWITCH_DECLARE(switch_status_t) switch_channel_set_variable_var_check(switch_channel_t *channel,const char *varname, const char *value, switch_bool_t var_check){switch_status_t status = SWITCH_STATUS_FALSE;switch_assert(channel != NULL);switch_mutex_lock(channel->profile_mutex);if (channel->variables && !zstr(varname)) {//如果变量的值为空, 则直接删除该变量if (zstr(value)) {switch_event_del_header(channel->variables, varname);} else {int ok = 1;//检查设置的变量值中,是否仍包含变量,比如 uuid_set xxx name=${local_ip_v4}if (var_check) {ok = !switch_string_var_check_const(value);}//检查通过后,将变量增加到event存储结构中if (ok) {switch_event_add_header_string(channel->variables, SWITCH_STACK_BOTTOM, varname, value);} else {switch_log_printf(SWITCH_CHANNEL_CHANNEL_LOG(channel), SWITCH_LOG_CRIT, "Invalid data (${%s} contains a variable)\n", varname);}}status = SWITCH_STATUS_SUCCESS;}switch_mutex_unlock(channel->profile_mutex);return status;}
获取通道变量
函数:switch_channel_get_variable(channel, varname)
调用方式: proxy_media = switch_channel_get_variable(caller_channel, SWITCH_PROXY_MEDIA_VARIABLE);
该函数为一个宏,具体如下:
//文件:switch_channel.hSWITCH_DECLARE(const char *) switch_channel_get_variable_dup(switch_channel_t *channel, const char *varname, switch_bool_t dup, int idx);#define switch_channel_get_variable(_c, _v) switch_channel_get_variable_dup(_c, _v, SWITCH_TRUE, -1)
该宏最终展开,调用的是switch_channel_get_variable_dup,只是其中的idx为-1,并且需要复制变量。
下面来看看具体是怎么实现的:
SWITCH_DECLARE(const char *) switch_channel_get_variable_dup(switch_channel_t *channel, const char *varname, switch_bool_t dup, int idx){const char *v = NULL, *r = NULL, *vdup = NULL;switch_assert(channel != NULL);switch_mutex_lock(channel->profile_mutex);//先检查变量是否为scoped variableif (channel->scope_variables) {switch_event_t *ep;for (ep = channel->scope_variables; ep; ep = ep->next) {if ((v = switch_event_get_header_idx(ep, varname, idx))) {break;}}}//再次检查变量是否位于通道变量中if (!v && (!channel->variables || !(v = switch_event_get_header_idx(channel->variables, varname, idx)))) {//不在通道变量中, 就先获取caller_profileswitch_caller_profile_t *cp = switch_channel_get_caller_profile(channel);//callerprofile: 检查变量是否以aleg_开头,是的话,就用originator_caller_profile当作caller_profileif (cp) {if (!strncmp(varname, "aleg_", 5)) {cp = cp->originator_caller_profile;varname += 5;} else if (!strncmp(varname, "bleg_", 5)) {cp = cp->originatee_caller_profile;varname += 5;}}//没有的话,就再到caller profile中查找//到caller profile中再查找一次if (!cp || !(v = switch_caller_get_field_by_name(cp, varname))) {//没找到,实在没辙了,就到全局变量中搜搜看if ((vdup = switch_core_get_variable_pdup(varname, switch_core_session_get_pool(channel->session)))) {v = vdup;}}}if (dup && v != vdup) {if (v) {r = switch_core_session_strdup(channel->session, v);}} else {r = v;}switch_mutex_unlock(channel->profile_mutex);return r;}
从代码中可以看出,查找变量的时候,依次遵循下面的顺序:
- scoped variable ->作用范围是application
- channel variable ->作用范围是session
- caller profile ->作用范围是主叫
- global variable ->作用范围是全局
也可以间接的看出各个变量的范围,从小检查到大!
关于scoped variable ,Anthony Minessale给出的解释是: add scoped channel variables (%[var=val,var2=val2] blocks valid in any app data field and will only last for that one app execution) 引用自:https://freeswitch.org/stash/projects/FS/repos/freeswitch/commits/b2c3199f653#src/switch_channel.c
删除变量
删除变量此处的常规操作是将变量的值赋为空字符串。
具体原理,可以参考前面设置变量的代码分析,可以看到下面的内容:
zsr的函数定义如下:
_Check_return_ static inline int _zstr(_In_opt_z_ const char *s){return !s || *s == '\0';}
只要字符串的指针为空, 或者字符串的大小为0,则都会将变量删除掉。
switch_event_del_header的函数定义如下(忽略宏展开):
基本的思路是channel->variables其实就是switch_event_t,从事件中获取headers链表,然后挨个去遍历查询,查到就从链表中去掉,然后释放所占的内存空间。
//文件: switch_event.c 832行SWITCH_DECLARE(switch_status_t) switch_event_del_header_val(switch_event_t *event, const char *header_name, const char *val){switch_event_header_t *hp, *lp = NULL, *tp;switch_status_t status = SWITCH_STATUS_FALSE;int x = 0;switch_ssize_t hlen = -1;unsigned long hash = 0;//所有的变量都保存在event的headers中,所以此处先获取引用,然后挨个遍历比对tp = event->headers;//此处获取变量名称的hash值,暂时没看懂具体意义hash = switch_ci_hashfunc_default(header_name, &hlen);while (tp) {hp = tp;tp = tp->next;x++;//最多查找100万次,一般情况下不会超过。switch_assert(x < 1000000);//删除需满足的条件如下://1. hash为空或者等同前面计算的值//2. 变量名称相等//3. 变量值为空,或者新变量值等同于原来的值if ((!hp->hash || hash == hp->hash) && !strcasecmp(header_name, hp->name) && (zstr(val) || !strcmp(hp->value, val))) {//从链表中将该节点去掉if (lp) {lp->next = hp->next;} else {event->headers = hp->next;}//如果该节点已经是最后一个,则重置last_headerif (hp == event->last_header || !hp->next) {event->last_header = lp;}FREE(hp->name);if (hp->idx) {int i = 0;for (i = 0; i < hp->idx; i++) {FREE(hp->array[i]);}FREE(hp->array);}FREE(hp->value);//将该节点的值清理掉,重新初始化memset(hp, 0, sizeof(*hp));//如果使用的event复用机制,则尝试将其压入EVENT_HEADER_RECYCLE_QUEUE中备用#ifdef SWITCH_EVENT_RECYCLEif (switch_queue_trypush(EVENT_HEADER_RECYCLE_QUEUE, hp) != SWITCH_STATUS_SUCCESS) {FREE(hp);//压入失败就拉倒,直接释放掉}#else//此处不用担心指针的重复释放问题,因为FREE中释放的时候,会先检查指针是否有效,无效直接跳过了。// #define FREE(ptr) switch_safe_free(ptr)//#define switch_safe_free(it) if (it) {free(it);it=NULL;}FREE(hp);#endifstatus = SWITCH_STATUS_SUCCESS;} else {lp = hp;}}return status;}
export变量
如果存在主叫和被叫,在呼叫被叫的时候,需要将主叫上面的export变量转给被叫。
//文件:switch_ivr_originate.c -> switch_ivr_originate()函数,2039行if (caller_channel) {switch_channel_process_export(caller_channel, NULL, var_event, SWITCH_EXPORT_VARS_VARIABLE);}
export的基本原理是,查找主叫变量中的export_vars,该变量上面会记录有哪几个变量需要导入到被叫中。
上面的宏SWITCH_EXPORT_VARS_VARIABLE,展开后就是“export_vars”
switch_channel_process_export具体的实现代码如下:
建议结合我的另一篇文档一同观看:关于export的原理介绍,这篇文档是在还没看代码的情况的总结的,基本和下面的代码都能对应上。
//文件:switch_channel.c, 1165行SWITCH_DECLARE(void) switch_channel_process_export(switch_channel_t *channel, switch_channel_t *peer_channel,switch_event_t *var_event, const char *export_varname){//获取待导出的遍历列表,其实就是以逗号隔开的变量字符串const char *export_vars = switch_channel_get_variable(channel, export_varname);//复制一份,以备后面的分割char *cptmp = switch_core_session_strdup(channel->session, export_vars);int argc;char *argv[256];//导出列表都是空的,不玩了,回了您赖if (zstr(export_vars)) return;//准备接收导出,先删除变量“export_vars”,然后将导出列表内容保存到“export_vars”变量中if (var_event) {switch_event_del_header(var_event, export_varname);switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, export_varname, export_vars);}if (peer_channel) {switch_channel_set_variable(peer_channel, export_varname, export_vars);}//分割待导出变量if ((argc = switch_separate_string(cptmp, ',', argv, (sizeof(argv) / sizeof(argv[0]))))) {int x;for (x = 0; x < argc; x++) {const char *vval;//根据变量名称,获取变量真正的值if ((vval = switch_channel_get_variable(channel, argv[x]))) {char *vvar = argv[x];//如果变量以nolocal:开头,则去掉该头,提取真正的变量名称if (!strncasecmp(vvar, "nolocal:", 8)) { /* remove this later ? */vvar += 8;} else if (!strncasecmp(vvar, "_nolocal_", 9)) {vvar += 9;}//将该变量设置到新通道中(去掉了nolcoal之类的头,如果有的话)if (var_event) {switch_event_del_header(var_event, vvar);switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, vvar, vval);switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(channel->session), SWITCH_LOG_DEBUG,"%s EXPORTING[%s] [%s]=[%s] to event\n",switch_channel_get_name(channel),export_varname,vvar, vval);}if (peer_channel) {switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(channel->session), SWITCH_LOG_DEBUG,"%s EXPORTING[%s] [%s]=[%s] to %s\n",switch_channel_get_name(channel),export_varname,vvar, vval, switch_channel_get_name(peer_channel));switch_channel_set_variable(peer_channel, vvar, vval);}}}}}
获取全局变量
获取全局变量主要通过下面的函数进行。
switch_core_get_variable
switch_core_get_variable_dup
文件: switch_core.c
SWITCH_DECLARE(char *) switch_core_get_variable(const char *varname){char *val;switch_thread_rwlock_rdlock(runtime.global_var_rwlock);val = (char *) switch_event_get_header(runtime.global_vars, varname);switch_thread_rwlock_unlock(runtime.global_var_rwlock);return val;}
从代码中可以看出,全局变量也是存在runtime上的event对象中。
可以复用通道变量一样的处理。
比如,获取domain,可以: switch_core_get_variable(“domain”)
同样,我们可以看看mod_comands.c里面global_getvar的实现方式:
#define GLOBAL_GETVAR_SYNTAX "<var>"SWITCH_STANDARD_API(global_getvar_function){if (zstr(cmd)) {switch_core_dump_variables(stream);} else {char *var = switch_core_get_variable_dup(cmd);stream->write_function(stream, "%s", switch_str_nil(var));switch_safe_free(var);}return SWITCH_STATUS_SUCCESS;}
和上面的switch_core_get_variable区别不大, 只是用了另一个switch_core_get_variable_dup方法,复制一份到新指针而已。
