通道变量存储与传递

从代码的注释可以看出,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

  1. struct switch_event_header {
  2. /*! the header name */
  3. char *name;
  4. /*! the header value */
  5. char *value;
  6. /*! array space */
  7. char **array;
  8. /*! array index */
  9. int idx;
  10. /*! hash of the header name */
  11. unsigned long hash;
  12. struct switch_event_header *next;
  13. };

可以看到,header中含有典型的key-val形式的数据,比较适合通道变量的存储。

Event

event数据结构如下:

  1. struct switch_event {
  2. /*! the event id (descriptor) */
  3. switch_event_types_t event_id;
  4. /*! the priority of the event */
  5. switch_priority_t priority;
  6. /*! the owner of the event */
  7. char *owner;
  8. /*! the subclass of the event */
  9. char *subclass_name;
  10. /*! the event headers */
  11. switch_event_header_t *headers;
  12. /*! the event headers tail pointer */
  13. switch_event_header_t *last_header;
  14. /*! the body of the event */
  15. char *body;
  16. /*! user data from the subclass provider */
  17. void *bind_user_data;
  18. /*! user data from the event sender */
  19. void *event_user_data;
  20. /*! unique key */
  21. unsigned long key;
  22. struct switch_event *next;
  23. int flags;
  24. };

主要使用headers来存储各个通道变量。
用于存储通道变量的事件类型比较特殊,是SWITCH_EVENT_CHANNEL_DATA。

简单关系图

通道变量代码小结 - 图1

基本操作

创建事件(特殊事件,只用于存储各种变量)

创建一个特殊的事件,类型是SWITCH_EVENT_CHANNEL_DATA

  1. //文件:switch_ivr_originate.c -> switch_ivr_originate()函数
  2. if (switch_event_create_plain(&var_event, SWITCH_EVENT_CHANNEL_DATA) != SWITCH_STATUS_SUCCESS) {
  3. abort();
  4. }

switch_event_create_plain函数函数内部创建的事件类型是SWITCH_EVENT_CLONE。
创建完成后,才重新改为SWITCH_EVENT_CHANNEL_DATA类型。
如下:

  1. //文件:switch_event.h
  2. static inline switch_status_t switch_event_create_plain(switch_event_t **event, switch_event_types_t event_id)
  3. {
  4. switch_status_t status = switch_event_create(event, SWITCH_EVENT_CLONE);
  5. if (status == SWITCH_STATUS_SUCCESS) {
  6. //创建事件成功后,将事件id由SWITCH_EVENT_CLONE修改为SWITCH_EVENT_CHANNEL_DATA
  7. (*event)->event_id = event_id;
  8. //设置事件的header不允许重复
  9. if (event_id == SWITCH_EVENT_REQUEST_PARAMS || event_id == SWITCH_EVENT_CHANNEL_DATA) {
  10. (*event)->flags |= EF_UNIQ_HEADERS;
  11. }
  12. }
  13. return status;
  14. }

再次往下跟踪switch_event_create:
switch_event_create实际是两层的宏,具体如下:

  1. //文件: switch_event.h
  2. #define switch_event_create(event, id) switch_event_create_subclass(event, id, SWITCH_EVENT_SUBCLASS_ANY)
  3. #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

上面这些大部分都是空的值,因为不是真正的事件,由宏屏蔽后,就可以少传这些参数。

下面再来到具体真正创建事件的地方(去掉了部分代码):

  1. SWITCH_DECLARE(switch_status_t) switch_event_create_subclass_detailed(const char *file, const char *func, int line,
  2. switch_event_t **event, switch_event_types_t event_id, const char *subclass_name)
  3. {
  4. ...
  5. *event = NULL;
  6. //分配事件内存空间,并进行初始化
  7. *event = ALLOC(sizeof(switch_event_t));
  8. switch_assert(*event);
  9. memset(*event, 0, sizeof(switch_event_t));
  10. if (event_id == SWITCH_EVENT_REQUEST_PARAMS || event_id == SWITCH_EVENT_CHANNEL_DATA || event_id == SWITCH_EVENT_MESSAGE) {
  11. (*event)->flags |= EF_UNIQ_HEADERS;
  12. }
  13. //这个才是正正经经的事件代码,需要初始化一些基本的的事件头,如Event-Name、Core-UUID、Event-Date-Local、Event-Calling-File等
  14. if (event_id != SWITCH_EVENT_CLONE) {
  15. (*event)->event_id = event_id;
  16. switch_event_prep_for_delivery_detailed(file, func, line, *event);
  17. }
  18. //发现是custom xxx事件的话,还得把子事件的名字写进来
  19. if (subclass_name) {
  20. (*event)->subclass_name = DUP(subclass_name);
  21. switch_event_add_header_string(*event, SWITCH_STACK_BOTTOM, "Event-Subclass", subclass_name);
  22. }
  23. return SWITCH_STATUS_SUCCESS;
  24. }

从代码中可以看出,我们创建的特殊事件(内部是SWITCH_EVENT_CLONE),是没有调用switch_event_prep_for_delivery_detailed进行普通事件的初始化的。
因为它本身就不是用来进行事件流转的,是用来存储数据的。

可以参考下这个 链接,讲的比较详细。

设置通道变量

switch_channel_set_variable
调用方式:switch_channel_set_variable(caller_channel, "bypass_media_after_bridge", "true");

该函数为一个宏,具体如下:

  1. //文件:switch_channel.h
  2. SWITCH_DECLARE(switch_status_t) switch_channel_set_variable_var_check(switch_channel_t *channel,
  3. const char *varname, const char *value, switch_bool_t var_check);
  4. #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来真正的添加变量。

  1. //文件:switch_channel.c
  2. SWITCH_DECLARE(switch_status_t) switch_channel_set_variable_var_check(switch_channel_t *channel,
  3. const char *varname, const char *value, switch_bool_t var_check)
  4. {
  5. switch_status_t status = SWITCH_STATUS_FALSE;
  6. switch_assert(channel != NULL);
  7. switch_mutex_lock(channel->profile_mutex);
  8. if (channel->variables && !zstr(varname)) {
  9. //如果变量的值为空, 则直接删除该变量
  10. if (zstr(value)) {
  11. switch_event_del_header(channel->variables, varname);
  12. } else {
  13. int ok = 1;
  14. //检查设置的变量值中,是否仍包含变量,比如 uuid_set xxx name=${local_ip_v4}
  15. if (var_check) {
  16. ok = !switch_string_var_check_const(value);
  17. }
  18. //检查通过后,将变量增加到event存储结构中
  19. if (ok) {
  20. switch_event_add_header_string(channel->variables, SWITCH_STACK_BOTTOM, varname, value);
  21. } else {
  22. switch_log_printf(SWITCH_CHANNEL_CHANNEL_LOG(channel), SWITCH_LOG_CRIT, "Invalid data (${%s} contains a variable)\n", varname);
  23. }
  24. }
  25. status = SWITCH_STATUS_SUCCESS;
  26. }
  27. switch_mutex_unlock(channel->profile_mutex);
  28. return status;
  29. }

获取通道变量

函数:switch_channel_get_variable(channel, varname)
调用方式: proxy_media = switch_channel_get_variable(caller_channel, SWITCH_PROXY_MEDIA_VARIABLE);

该函数为一个宏,具体如下:

  1. //文件:switch_channel.h
  2. SWITCH_DECLARE(const char *) switch_channel_get_variable_dup(switch_channel_t *channel, const char *varname, switch_bool_t dup, int idx);
  3. #define switch_channel_get_variable(_c, _v) switch_channel_get_variable_dup(_c, _v, SWITCH_TRUE, -1)

该宏最终展开,调用的是switch_channel_get_variable_dup,只是其中的idx为-1,并且需要复制变量。

下面来看看具体是怎么实现的:

  1. SWITCH_DECLARE(const char *) switch_channel_get_variable_dup(switch_channel_t *channel, const char *varname, switch_bool_t dup, int idx)
  2. {
  3. const char *v = NULL, *r = NULL, *vdup = NULL;
  4. switch_assert(channel != NULL);
  5. switch_mutex_lock(channel->profile_mutex);
  6. //先检查变量是否为scoped variable
  7. if (channel->scope_variables) {
  8. switch_event_t *ep;
  9. for (ep = channel->scope_variables; ep; ep = ep->next) {
  10. if ((v = switch_event_get_header_idx(ep, varname, idx))) {
  11. break;
  12. }
  13. }
  14. }
  15. //再次检查变量是否位于通道变量中
  16. if (!v && (!channel->variables || !(v = switch_event_get_header_idx(channel->variables, varname, idx)))) {
  17. //不在通道变量中, 就先获取caller_profile
  18. switch_caller_profile_t *cp = switch_channel_get_caller_profile(channel);
  19. //callerprofile: 检查变量是否以aleg_开头,是的话,就用originator_caller_profile当作caller_profile
  20. if (cp) {
  21. if (!strncmp(varname, "aleg_", 5)) {
  22. cp = cp->originator_caller_profile;
  23. varname += 5;
  24. } else if (!strncmp(varname, "bleg_", 5)) {
  25. cp = cp->originatee_caller_profile;
  26. varname += 5;
  27. }
  28. }
  29. //没有的话,就再到caller profile中查找
  30. //到caller profile中再查找一次
  31. if (!cp || !(v = switch_caller_get_field_by_name(cp, varname))) {
  32. //没找到,实在没辙了,就到全局变量中搜搜看
  33. if ((vdup = switch_core_get_variable_pdup(varname, switch_core_session_get_pool(channel->session)))) {
  34. v = vdup;
  35. }
  36. }
  37. }
  38. if (dup && v != vdup) {
  39. if (v) {
  40. r = switch_core_session_strdup(channel->session, v);
  41. }
  42. } else {
  43. r = v;
  44. }
  45. switch_mutex_unlock(channel->profile_mutex);
  46. return r;
  47. }

从代码中可以看出,查找变量的时候,依次遵循下面的顺序:

  1. scoped variable ->作用范围是application
  2. channel variable ->作用范围是session
  3. caller profile ->作用范围是主叫
  4. 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

删除变量

删除变量此处的常规操作是将变量的值赋为空字符串。
具体原理,可以参考前面设置变量的代码分析,可以看到下面的内容:
image.png

zsr的函数定义如下:

  1. _Check_return_ static inline int _zstr(_In_opt_z_ const char *s)
  2. {
  3. return !s || *s == '\0';
  4. }

只要字符串的指针为空, 或者字符串的大小为0,则都会将变量删除掉。

switch_event_del_header的函数定义如下(忽略宏展开):
基本的思路是channel->variables其实就是switch_event_t,从事件中获取headers链表,然后挨个去遍历查询,查到就从链表中去掉,然后释放所占的内存空间。

  1. //文件: switch_event.c 832行
  2. SWITCH_DECLARE(switch_status_t) switch_event_del_header_val(switch_event_t *event, const char *header_name, const char *val)
  3. {
  4. switch_event_header_t *hp, *lp = NULL, *tp;
  5. switch_status_t status = SWITCH_STATUS_FALSE;
  6. int x = 0;
  7. switch_ssize_t hlen = -1;
  8. unsigned long hash = 0;
  9. //所有的变量都保存在event的headers中,所以此处先获取引用,然后挨个遍历比对
  10. tp = event->headers;
  11. //此处获取变量名称的hash值,暂时没看懂具体意义
  12. hash = switch_ci_hashfunc_default(header_name, &hlen);
  13. while (tp) {
  14. hp = tp;
  15. tp = tp->next;
  16. x++;
  17. //最多查找100万次,一般情况下不会超过。
  18. switch_assert(x < 1000000);
  19. //删除需满足的条件如下:
  20. //1. hash为空或者等同前面计算的值
  21. //2. 变量名称相等
  22. //3. 变量值为空,或者新变量值等同于原来的值
  23. if ((!hp->hash || hash == hp->hash) && !strcasecmp(header_name, hp->name) && (zstr(val) || !strcmp(hp->value, val))) {
  24. //从链表中将该节点去掉
  25. if (lp) {
  26. lp->next = hp->next;
  27. } else {
  28. event->headers = hp->next;
  29. }
  30. //如果该节点已经是最后一个,则重置last_header
  31. if (hp == event->last_header || !hp->next) {
  32. event->last_header = lp;
  33. }
  34. FREE(hp->name);
  35. if (hp->idx) {
  36. int i = 0;
  37. for (i = 0; i < hp->idx; i++) {
  38. FREE(hp->array[i]);
  39. }
  40. FREE(hp->array);
  41. }
  42. FREE(hp->value);
  43. //将该节点的值清理掉,重新初始化
  44. memset(hp, 0, sizeof(*hp));
  45. //如果使用的event复用机制,则尝试将其压入EVENT_HEADER_RECYCLE_QUEUE中备用
  46. #ifdef SWITCH_EVENT_RECYCLE
  47. if (switch_queue_trypush(EVENT_HEADER_RECYCLE_QUEUE, hp) != SWITCH_STATUS_SUCCESS) {
  48. FREE(hp);//压入失败就拉倒,直接释放掉
  49. }
  50. #else
  51. //此处不用担心指针的重复释放问题,因为FREE中释放的时候,会先检查指针是否有效,无效直接跳过了。
  52. // #define FREE(ptr) switch_safe_free(ptr)
  53. //#define switch_safe_free(it) if (it) {free(it);it=NULL;}
  54. FREE(hp);
  55. #endif
  56. status = SWITCH_STATUS_SUCCESS;
  57. } else {
  58. lp = hp;
  59. }
  60. }
  61. return status;
  62. }

export变量

如果存在主叫和被叫,在呼叫被叫的时候,需要将主叫上面的export变量转给被叫。

  1. //文件:switch_ivr_originate.c -> switch_ivr_originate()函数,2039行
  2. if (caller_channel) {
  3. switch_channel_process_export(caller_channel, NULL, var_event, SWITCH_EXPORT_VARS_VARIABLE);
  4. }

export的基本原理是,查找主叫变量中的export_vars,该变量上面会记录有哪几个变量需要导入到被叫中。
上面的宏SWITCH_EXPORT_VARS_VARIABLE,展开后就是“export_vars”

switch_channel_process_export具体的实现代码如下:

建议结合我的另一篇文档一同观看:关于export的原理介绍,这篇文档是在还没看代码的情况的总结的,基本和下面的代码都能对应上。

  1. //文件:switch_channel.c, 1165行
  2. SWITCH_DECLARE(void) switch_channel_process_export(switch_channel_t *channel, switch_channel_t *peer_channel,
  3. switch_event_t *var_event, const char *export_varname)
  4. {
  5. //获取待导出的遍历列表,其实就是以逗号隔开的变量字符串
  6. const char *export_vars = switch_channel_get_variable(channel, export_varname);
  7. //复制一份,以备后面的分割
  8. char *cptmp = switch_core_session_strdup(channel->session, export_vars);
  9. int argc;
  10. char *argv[256];
  11. //导出列表都是空的,不玩了,回了您赖
  12. if (zstr(export_vars)) return;
  13. //准备接收导出,先删除变量“export_vars”,然后将导出列表内容保存到“export_vars”变量中
  14. if (var_event) {
  15. switch_event_del_header(var_event, export_varname);
  16. switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, export_varname, export_vars);
  17. }
  18. if (peer_channel) {
  19. switch_channel_set_variable(peer_channel, export_varname, export_vars);
  20. }
  21. //分割待导出变量
  22. if ((argc = switch_separate_string(cptmp, ',', argv, (sizeof(argv) / sizeof(argv[0]))))) {
  23. int x;
  24. for (x = 0; x < argc; x++) {
  25. const char *vval;
  26. //根据变量名称,获取变量真正的值
  27. if ((vval = switch_channel_get_variable(channel, argv[x]))) {
  28. char *vvar = argv[x];
  29. //如果变量以nolocal:开头,则去掉该头,提取真正的变量名称
  30. if (!strncasecmp(vvar, "nolocal:", 8)) { /* remove this later ? */
  31. vvar += 8;
  32. } else if (!strncasecmp(vvar, "_nolocal_", 9)) {
  33. vvar += 9;
  34. }
  35. //将该变量设置到新通道中(去掉了nolcoal之类的头,如果有的话)
  36. if (var_event) {
  37. switch_event_del_header(var_event, vvar);
  38. switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, vvar, vval);
  39. switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(channel->session), SWITCH_LOG_DEBUG,
  40. "%s EXPORTING[%s] [%s]=[%s] to event\n",
  41. switch_channel_get_name(channel),
  42. export_varname,
  43. vvar, vval);
  44. }
  45. if (peer_channel) {
  46. switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(channel->session), SWITCH_LOG_DEBUG,
  47. "%s EXPORTING[%s] [%s]=[%s] to %s\n",
  48. switch_channel_get_name(channel),
  49. export_varname,
  50. vvar, vval, switch_channel_get_name(peer_channel));
  51. switch_channel_set_variable(peer_channel, vvar, vval);
  52. }
  53. }
  54. }
  55. }
  56. }

获取全局变量

获取全局变量主要通过下面的函数进行。
switch_core_get_variable
switch_core_get_variable_dup

文件: switch_core.c

  1. SWITCH_DECLARE(char *) switch_core_get_variable(const char *varname)
  2. {
  3. char *val;
  4. switch_thread_rwlock_rdlock(runtime.global_var_rwlock);
  5. val = (char *) switch_event_get_header(runtime.global_vars, varname);
  6. switch_thread_rwlock_unlock(runtime.global_var_rwlock);
  7. return val;
  8. }

从代码中可以看出,全局变量也是存在runtime上的event对象中。
可以复用通道变量一样的处理。

比如,获取domain,可以: switch_core_get_variable(“domain”)

同样,我们可以看看mod_comands.c里面global_getvar的实现方式:

  1. #define GLOBAL_GETVAR_SYNTAX "<var>"
  2. SWITCH_STANDARD_API(global_getvar_function)
  3. {
  4. if (zstr(cmd)) {
  5. switch_core_dump_variables(stream);
  6. } else {
  7. char *var = switch_core_get_variable_dup(cmd);
  8. stream->write_function(stream, "%s", switch_str_nil(var));
  9. switch_safe_free(var);
  10. }
  11. return SWITCH_STATUS_SUCCESS;
  12. }

和上面的switch_core_get_variable区别不大, 只是用了另一个switch_core_get_variable_dup方法,复制一份到新指针而已。