通道变量存储与传递
从代码的注释可以看出,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.h
static 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.h
SWITCH_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.c
SWITCH_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.h
SWITCH_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 variable
if (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_profile
switch_caller_profile_t *cp = switch_channel_get_caller_profile(channel);
//callerprofile: 检查变量是否以aleg_开头,是的话,就用originator_caller_profile当作caller_profile
if (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_header
if (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_RECYCLE
if (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);
#endif
status = 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方法,复制一份到新指针而已。