您的当前位置:首页正文

FreeSwitch的初始化及其模块加载过程

2024-01-05 来源:欧得旅游网
FreeSwitch的初始化及其模块加载过程

Freeswitch的主函数是在文件switch.c中定义的,该文件的260行是整个程序的入口,主函数主要完成的功能是包括,命令行解析,初始化apr库,构建全局内存池,模块加载和初始化核心组件。

初始化apr库是由apr_initialize()函数完成的,apr库是apache的可移植动态库,完成相关的内存池,线程管理的跨平台工作。该函数的调用在主函数的659行。

745行的switch_core_set_globals()主要是完成全局目录的设置。不过,在switch_core_init()中再一次调用了该函数。

747行的pid= getpid()获取程序的进程号。

754行利用apr_pool_create()创建一个匿名的内存池,由主函数中定义的switch_memory_pool_t*pool局部指针指向,但是可以知道,该内存池将作为程序的整个运行周期所使用。

本分析最关键的一点出现在784行,该行调用了switch_core_init_and_modload()函数,该函数完成了核心组件的初始化以及各个模块的动态加载。最终,形成了一个统一的系统。

switch_core_init_and_modload()

函数定义在switch_core.c文件中,第1526行。函数原型如下: SWITCH_DECLARE(switch_status_t)

switch_core_init_and_modload(switch_core_flag_tflags, switch_bool_t console, const char **err)

其中,SWITCH_DECLARE(type)宏在windows下展开为 #define SWITCH_DECLARE(type) __declspec(dllexport) type

__stdcall

主要用于将函数声明为dll的导出符号,这样,在其他模块中,便可以使用该函数了。而在其他系统平台上,该宏是一个空宏,例如在linux下,共享 库的符号是全局的,不需要声明为导出符号。一般来说,freeswitch其他的动态加载模块所定义的函数不需要用该宏声明,在windows平台下,各 个模块之间是隔离的,而核心模块中定义的函数大部分使用了该宏声明,因为其他模块需要大量使用核心模块中的核心函数,这里所指的核心模块是 FreeSwitchCoreLib共享对象。

于是可以知道,switch_core_init_and_modload()函数可以在其他依赖于核心模块的动态加载模块中使用,这里主函数所在的模块是FreeSwitchConsole,依赖于核心模块,于是,便可以使用该函数来完成模块加载。

switch_core_init()

在该函数中调用了switch_core_init()函数,用来初始化一些全局化的信息,包括一个全局的switch_runtime结构,各种全局的哈希表,互斥变量。一条一条地分心如下:

① 全局的switch_runtime结构runtime部分字段的初始化—— 代码段如下:

if(runtime.runlevel > 0) { /* one percustomer */

returnSWITCH_STATUS_SUCCESS; }

runtime.runlevel++;//从这里可见,runlevel大于0是一个服务器已启动的标志,所以不必在进行

//以下的初始化操作,直接返回SWITCH_STATUS_SUCCESS即可。

runtime.dummy_cng_frame.data =runtime.dummy_data;

runtime.dummy_cng_frame.datalen sizeof(runtime.dummy_data);

runtime.dummy_cng_frame.buflen sizeof(runtime.dummy_data);

switch_set_flag((&runtime.dummy_cng_frame),SFF_CNG);

switch_set_flag((&runtime),SCF_NO_NEW_SESSIONS); runtime.hard_log_level = SWITCH_LOG_DEBUG; runtime.mailer_app = \"sendmail\"; runtime.mailer_app_args = \"-t\"; runtime.max_dtmf_duration =SWITCH_MAX_DTMF_DURATION;

runtime.default_dtmf_duration =SWITCH_DEFAULT_DTMF_DURATION;

runtime.min_dtmf_duration= SWITCH_MIN_DTMF_DURATION;

= =

接下来又重新初始化了一遍apr库,很奇怪,不知道是不是一个多余的步骤。^_^

/* INIT APR andCreate the pool context */ if(apr_initialize() != SWITCH_STATUS_SUCCESS) { *err = \"FATALERROR! Could not initialize APR\\n\"; returnSWITCH_STATUS_MEMERR; }

if(!(runtime.memory_pool = switch_core_memory_init())) { *err = \"FATALERROR! Could not allocate memory pool\\n\"; returnSWITCH_STATUS_MEMERR;

}//从这里可以看见,全局的runtime是有一个内存池来管理它所需要的其他资源的。

② 安装时的目录信息的相关设置,与runtime结构挂钩,代码如

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.base_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//主目录,即工程所在目录,一般为./bin,./表示安装路径

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.mod_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//模块所在目录,一般为安装目录./mod,./表示安装路径

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.conf_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//配置文件所在目录,一般为./conf

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.log_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//日志所在的目录。一般为./log

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.run_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//进程文件所在目录,一般为./run,进程文件为freeswitch.pid

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.db_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//数据库文件所在目录,一般为./db

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.script_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//脚本文件所在目录,一般为./script,存放系统需要执行的脚本文件,

//比较常用的由javascript脚本和lua脚本。

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.htdocs_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.grammar_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.recordings_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//录音文件所在目录

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.sounds_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//声音文件所在目录。

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.temp_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//临时目录。

③ 全局的互斥变量和哈希表初始化,代码片段如下: switch_mutex_init(&runtime.uuid_mutex,SWITCH_MUTEX_NESTED, runtime.memory_pool);

switch_mutex_init(&runtime.throttle_mutex,SWITCH_MUTEX_NESTED, runtime.memory_pool);

switch_mutex_init(&runtime.session_hash_mutex,SWITCH_MUTEX_NESTED, runtime.memory_pool);

switch_mutex_init(&runtime.global_mutex,SWITCH_MUTEX_NESTED, runtime.memory_pool);

switch_mutex_init(&runtime.global_var_mutex,SWITCH_MUTEX_NESTED, runtime.memory_pool);

以及

switch_core_hash_init(&runtime.global_vars,runtime.memory_pool);

switch_core_hash_init(&runtime.mime_types,runtime.memory_pool);

④ 系统相关的很重要的初始化 1. switch_core_set_globals()

由于在主函数中已经设置好了各个安装目录,所以此次调用将不做任何实际意义的工作。

2. switch_core_session_init(runtime.memory_pool) 3. load_mime_types()

4. gethostname(hostname, sizeof(hostname))获取主机名 5. switch_find_local_ip(guess_ip,sizeof(guess_ip), &mask,

AF_INET)获取主机的ip地址。这里主要是获取ipv4的地址,下面还要重新调用一次该函数获取ipv6的地址。

6. switch_console_init(runtime.memory_pool)初始化控制台。函数的实际代码如下:

a) SWITCH_DECLARE(switch_status_t)switch_console_init(switch_memory_pool_t *pool)

b) {

c) switch_mutex_init(&globals.func_mutex,SWITCH_MUTEX_NESTED, pool);

d) switch_core_hash_init(&globals.func_hash,pool); e) switch_console_add_complete_func(\"::console::list_uuid\",(switch_console_complete_callback_t) switch_console_list_uuid);

f) returnSWITCH_STATUS_SUCCESS; g) }

7. switch_event_init(runtime.memory_pool)

freeswitch整个系统的事件机 制,这个初始化很重要,在函数内部除了初始化一些互斥量,哈希队列,还创建了三个用于事件循环的队列,然后启动三个线程,分别代表了三个队列的时间循环处 理线程。而所有的资源,都有runtime.memory_pool进行管理,event事件的循环处理见后续分析。

8. switch_xml_init(runtime.memory_pool,err)进行xml配置文件相关的初始化。

9. switch_log_init(runtime.memory_pool,runtime.colorize_console)日志系统的初始化。

10. switch_load_core_config(\"switch.conf\")读取全局的配置文件,然后根据该配置文件中的指令,依次读取后续的子目录下面的各个配置文件,详见后续分析。

11. switch_core_state_machine_init(runtime.memory_pool)state_machine 是整个FS系统的核心部位了,即通话状态机,根据各个channel的状态执行相应的状态处理函数,见后续分析。此处的函

数为空函数。

12. switch_core_sqldb_start()sql数据库的相关初始化。 13. switch_rtp_init(runtime.memory_pool)rtp协议的初始化。函数内调用srtp_init()初始化rtp协议栈,freeswitch所用的rtp库是libsrtp。

14. switch_scheduler_add_task(switch_epoch_time_now(NULL),heartbeat_callback,

\"heartbeat\

\"core\

0,

NULL,

SSHF_NONE |SSHF_NO_DEL)

在freeswitch中有一个task调度机制,这里讲heartbeat加入到task队列中。事件由

switch_scheduler_task_container_tswitch_scheduler.c中,通过全局的

static struct {

switch_scheduler_task_container_t*task_list; switch_mutex_t*task_mutex; uint32_t task_id; int task_thread_running;

switch_memory_pool_t *memory_pool; } globals;

Globals变量对task队列进行管理。Task的调度的线程也是在switch_core_init()中启动 的,具体的启动函数时switch_scheduler_task_thread_start().该函数内部生成的线程主函数为 switch_scheduler_task_thread():函数里有主循环;

while (globals.task_thread_running == 1) { if(task_thread_loop(0)) { break; }

switch_yield(500000);

}通过层层剥离,会进入task_thread_loop中一次执行挂接在

结构描述,

switch_scheduler.c中得全局globals的task队列。

switch_loadable_module_init()

switch_core_init_and_modload()

switch_loadable_module_init(),这里就 是根据目录信息加载各个动态模块的地方了。函数定义在switch_loadable_module.c文件中,属于核心组件的一部分。

函数内根据平台做了相关处理,在win32平台下,还需要通过函数switch_loadable_module_path_init()获取环境 变量的相关信息。另外需要注意的是,该函数内部重新重新生成了一个memory_pool不再是上面描述的runtime的memory_pool了。代 码如下:

switch_core_new_memory_pool(&loadable_modules.pool); 其中loadable_modules是一个文件作用域范围的全局量, static

structswitch_loadable_module_container

loadable_modules;类型为

switch_loadable_module_container,定义如下:

//************* switch_loadable_module_container的定义*****************************//

structswitch_loadable_module_container {

switch_hash_t *module_hash;//存放各个模块结构的哈希表指针 switch_hash_t *endpoint_hash;// 存放各个endpoint_interface的哈希表指针

switch_hash_t *codec_hash; // 存放各个codec_interface的哈希表指针

switch_hash_t *dialplan_hash; // 存放各个diaplan_interface的哈希表指针

switch_hash_t *timer_hash;// // 存放各个计时器的哈希表指针 switch_hash_t

*application_hash;//

application_interface的哈希表指针

switch_hash_t *api_hash; // 存放各个api_interface的哈希表指针

switch_hash_t *file_hash; switch_hash_t *speech_hash; switch_hash_t *asr_hash; switch_hash_t *directory_hash; switch_hash_t *chat_hash; switch_hash_t *say_hash;

switch_hash_t *management_hash; switch_mutex_t *mutex;//全局互斥量

switch_memory_pool_t *pool;//用于模块相关的apr内存池 };

该结构包含了若该的哈希表指针,分别指向存放各个接口结构的哈希表。

//***********************************************************************************//

接下来函数初始化了用于存放各个接口的哈希表,以及全局互斥量。

该函数是通过switch_loadable_module_load_module_ex((char *) SWITCH_GLOBAL_dirs.mod_dir, (char *) val, SWITCH_FALSE, global, &err)函数加载模块的。可见这里使用到了模块的目录信息SWITCH_GLOBAL_dirs.mod_dir。

switch_loadable_module_load_module_ex() 函数原型为: static

switch_status_t

switch_loadable_module_load_module_ex(char *dir, char*fname, switch_bool_t runtime, switch_bool_t global, constchar **err)

这里讲该函数顶定义成了一个static类型,只能在本文件中被调用。dir是上面个传下来的目录信息,fname是读取配置文件得到的需要加载的 动态对象名(例如mod_conference.dll,mod_sofia.dll或mod_conference.so,mod_sofia.so 等)

在该函数中,通过以下两个函数完成动态对象的加载: 1. switch_loadable_module_load_file(path,file,

global,

&new_module),这里我是用了调用时的实参,globals并非上面提出的全局管理结构,而是一个SWITCH_STATUS的枚举对 象。Path是加上了路径的完整文件名,而file仍然是配置文件中取得的名称。New_module是一个秒速模块的结构对象,具体的类型为

a) struct switch_loadable_module { b) char *key; c) char*filename; d) int perm;

e) switch_loadable_module_interface_t*module_interface;

f) switch_dso_lib_t lib;

g) switch_module_load_t switch_module_load; h) switch_module_runtime_tswitch_module_runtime; i) switch_module_shutdown_tswitch_module_shutdown; j) switch_memory_pool_t *pool; k) switch_status_t status; l) switch_thread_t *thread; m) switch_bool_t shutting_down; n) calltime_t *time_record; o) };

在switch_loadable_module_load_module_ex函数的开始出定

switch_loadable_module_t*new_module = NULL;

在switch_loadable_module_load_file函数中,会为每一个模块生成一个资源池

switch_core_new_memory_pool(&pool);

2. switch_loadable_module_process(file,new_module)函数主要是将new_module以及 module中定义的各个接口结构加入全局哈希表,在插入哈希表的过程中,由loadable_modules.mutex进行临界保护,举例如下:

① switch_core_hash_insert(loadable_modules.module_hash, key,new_module);//将new_module

//插入loadable_modules.module_hash指向的哈希表。 ②

(new_module->module_interface->endpoint_interface){

constswitch_endpoint_interface_t *ptr; for =ptr->next) {

switch_core_hash_insert(loadable_modules.endpoint_hash,ptr->interface_name, (const void *) ptr);

}//end if //若

new_module

module_interface

中包含

endpoint_interface,则将该endpoint_interface插入全局的endpoint_interface哈希表。

至此,模块加载也结束了。各个模块加载后各自进入自己的主线程中循环处理。

posted @ 2012-11-20 22:54 einyboy 阅读(297) 评论(0) 编辑 运行 FreeSWITCH

我建了一个 Freeswitch 内核研究 交流群, 45211986, 欢迎加入, 另外,提供基于SIP的通信服务器及客户端解决方案。

(ptr

=new_module->module_interface->endpoint_interface; ptr; ptr

if

读到本章,你应该对 FreeSWITCH 有了一个比较全面的了解,迫切地想实验它强大的功能了。让我们从最初的运行开始,一步一步进入 FreeSWITCH 的神秘世界。 命令行参数

一般来说,FreeSWITCH 不需要任何命令行参数就可以启动,但在某些情况下,你需要以一些特殊的参数启动。在此,仅作简单介绍。如果你知道是什么意思,那么你就可以使用,如果不知道,多半你用不到。

使用 freeswitch -help 或 freeswitch --help 会显示以下信息: -nf -- no forking

-u [user] -- 启动后以非 root 用户 user 身份运行 -g [group] -- 启动后以非 root 组 group 身份运行 -help -- 显示本帮助信息 -version -- 显示版本信息

-waste -- 允许浪费内存,FreeSWITCH 仅需 240K 的栈空间

你可以使用 ulimit -s 240 限制栈空间使用,或使用该选择忽略警告信息

-core -- 出错时进行内核转储 -hp -- 以高优先级运行

-vg -- 在 valgrind 下运行,调试内存泄露时使用 -nosql -- 不使用 SQL,show channels 类的命令将不能显示结果

-heavy-timer -- 更精确的时钟。可能会更精确,但对系统要求更高

-nonat -- 如果路由器支持 uPnP 或 NAT-PMP,则 FreeSWITCH

可以自动解决 NAT 穿越问题。如果路由器不支持,则该选项可以

使启动更快

-nocal -- 关闭时钟核准。FreeSWTICH 理想的运行环境是 1000 Hz 的内核时钟

如果你的内核时钟小于 1000 Hz 或在虚拟机上,可以尝试关闭该选项

-nort -- 关闭实时时钟

-stop -- 关闭 FreeSWTICH,它会在 run 目录中查找 PID文件

-nc -- 启动到后台模式,没有控制台 -c -- 启动到控制台,默认

-conf [confdir] -- 指定其它的配置文件所在目录,须与 -log、 -db 合用

-log [logdir] -- 指定其它的日志目录

-run [rundir] -- 指定其它存放 PID 文件的运行目录 -db [dbdir] -- 指定其它数据库目录 -mod [moddir] -- 指定其它模块目录 -htdocs [htdocsdir] -- 指定其它 HTTP 根目录 -scripts [scriptsdir] -- 指定其它脚本目录 系统启动脚本

在学习调试阶段,你可以启动到前台,而系统真正运行时,你可以使用 -nc 参数启动到后台,然后通过查看 log/freeswitch.log 跟踪系统运行情况(你可以用 tail -f 命令实时跟踪,我一般使用 less)。

一般情况下,启动到前台更容易调试,但你又不想在每次关闭 Terminal 时停止 FreeSWITCH,那么,你可以借助 screen 来实现。

在真正的生产系统上,你需要它能跟系统一起启动。在 *nix 系统上,启动脚本一般放在 /etc/init.d/。你可以在系统源代码目录下找到不同系统启动脚本 debian/freeswitch.init 及 build/freeswitch.init.*,参考使用。在 Windows 上,你也可以注册为 Windows 服务,参见附录中的 FAQ。 控制台与命令客户端

系统不带参数会启动到控制台,在控制台上你可以输入各种命令以控制或查询 FreeSWITCH 的状态。试试输入以下命令:

version -- 显示当前版本 status -- 显示当前状态 sofia status -- 显示 sofia 状态 help -- 显示帮助 为

便

FreeSWITCH

conf/autoload_configs/switch.conf.xml 中定义了一些控制台快捷键。你可以通过F1-F12来使用它们(不过,在某些操作系统上,有些快捷键可能与操作系统的相冲突,那你就只直接输入这些命令或 重新定义他们了)。

FreeSWITCH 是 Client-Server结构,不管 FreeSWITCH 运行在前台还是后台,你都可以使用客户端软件 fs_cli 连接 FreeSWITCH.

fs_cli 是一个类似 Telnet 的客户端(也类似于 Asterisk 中的 asterisk -r命令),它使用 FreeSWITCH 的 ESL(Event Socket Library)库与 FreeSWITCH 通信。当然,需要加载模块

mod_event_socket。该模块是默认加载的。

正常情况下,直接输入 bin/fs_cli 即可连接上,并出现系统提示符。如果出现:

[ERROR] libs/esl/fs_cli.c:652 main() Error Connecting [Socket Connection Error]

这样的错误,说明 FreeSWITCH 没有启动或 mod_event_socket 没有正确加载,请检查TCP端口8021端口是否处于监听状态或被其它进程占用。

fs_cli 也支持很多命令行参数,值得一提的是 -x 参数,它允许执行一条命令后退出,这在编写脚本程序时非常有用(如果它能支持管道会更有用,但是它不支持):

bin/fs/_cli -x \"version\" bin/fs/_cli -x \"status\"

其它的参数都可以通过配置文件来实现,在这里就不多说了。可以参见:

使用fs_cli,不仅可以连接到本机的 FreeSWITCH,也可以连接到其它机器的 FreeSWITCH 上(或本机另外的 FreeSWITCH 实例上),通过在用户主目录下编辑配置文件.fs_cli_conf(注意前面的点“.”),可以定义要连接的多个机器:

[server1]

host => 192.168.1.10 port => 8021

password => secret_password debug => 7

[server2]

host => 192.168.1.11 port => 8021

password => someother_password debug => 0

注意:如果要连接到其它机器,要确保 FreeSWITCH 的 Event Socket 是监听在真实网卡的 IP 地址上,而不是127.0.0.1。另外,在UNIX中,以点开头的文件是隐藏文件,普通的ls 命令是不能列出它的,可以使用 ls -a。

一旦配置好,就可以这样使用它: bin/fs_cli server1 bin/fs_cli server2

在 fs_cli 中,有几个特殊的命令,它们是以 “/” 开头的,这些命令并不直接发送到 FreeSWITCH,而是先由 fs_cli 处理。/quit、/bye、/exit、Ctrl + D 都可以退出 fs_cli;/help是帮助。

其它一些 “/”开头的指令与 Event Socket 中相关的命令相同,如:

/event -- 开启事件接收 /noevents -- 关闭事件接收

/nixevent -- 除了特定一种外,开启所有事件

/log -- 设置 log 级别,如 /log info 或 /log debug 等 /nolog -- 关闭 log /filter -- 过滤事件

另外,一些“重要”命令不能直接在 fs_cli 中执行,如 shutdown 命令,在控制台上可以直接执行,但在 fs_cli中,需要执行 fsctl shutdown。

除此之外,其它命令都与直接在 FreeSWITCH 控制台上执行是一样的。它也支持快捷键,最常用的快捷键是 F6(reloadxml)、F7(关闭 log输出)、F8(开启 debug 级别的 log 输出)。

在 *nix上,两者都通过 libeditline 支持命令行编辑功能。可以通过上、下箭头查看命令历史。 发起呼叫

可以在 FreeSWITCH 中使用 originate 命令发起一次呼叫,如果用户 1000 已经注册,那么:

originate user/alice &echo

上述命令在呼叫 1000 这个用户后,便执行 echo 这个程序。echo 是一个回音程序,即它会把任何它“听到”的声音(或视频)再返回(说)给对方。因此,如果这时候用户 1000 接了电话,无论说什么都能听到自己的声音。 呼叫字符串

上面的例子中,user/alice 称为呼叫字符串,或呼叫 URL。user 是一种特殊的呼叫字符串。我们先来复习一下第四章的场景。FreeSWITCH UA 的地址为 192.168.4.4:5050,alice UA 的地址为 192.168.4.4:5090,bob UA 的地址为 192.168.4.4:26000。若 alice 已向 FreeSWITCH 注册,在 FreeSWITCH 中就可以看到她的注册信息:

**********************************>sofia status profile internal reg

Registrations:

=============================================================================================

Call-ID:

ZTRkYjdjYzY0OWFhNDRhOGFkNDUxMTdhMWJhNjRmNmE.

User:*************.4.4Contact: port=UDP>

Agent: Zoiper rev.5415

Status: Registered(UDP)(unknown) EXP(2010-05-02 18:10:53)

Host: du-sevens-mac-pro.local IP: 192.168.4.4 Port: 5090

\"Alice\"

Auth-User: alice Auth-Realm: 192.168.4.4 MWI-Account:*************.4.4

=============================================================================================

FreeSWITCH 根据 Contact 字段知道 alice 的 SIP 地址 sip:*************.4.4:5090。当使用 originate 呼叫 user/alice 这个地址时,FreeSWITCH 便查找本地数据库,向 alice 的地址 sip:*************.4.4:5090发送 INVITE 请求(实际的呼叫字符串是由用户目录中的 dial-string 参数决定的)。 API 与 APP

在上面的例子中,originate 是一个命令(Command),它用于控制 FreeSWITCH 发起一个呼叫。FreeSWITCH 的命令不仅可以在控制台上使用,也可以在各种嵌入式脚本、Event Socket (fs_cli 就是使用了 ESL库)或 HTTP RPC 上使用,所有命令都遵循一个抽像的接口,因而这些命令又称 API Commands。

echo() 则是一个程序(Application,简称 APP),它的作用是控制一个 Channel 的一端。我们知道,一个 Channel 有两端,在上面的例子中,alice 是一端,别一端就是 echo()。电话接通后相当于 alice 在跟 echo() 这个家伙在通话。另一个常用的 APP 是 park()

originate user/alice &park()

我们初始化了一个呼叫,在 alice 接电话后对端必须有一个人在跟也讲话,否则的话,一个 Channel 只有一端,那是不可思议的。而如果这时 FreeSWITCH 找不到一个合适的人跟 alice 通话,那么它可以将该电话“挂起”,park()便是执行这个功能,它相当于一个 Channel 特殊的一端。

park() 的用户体验不好,alice 不知道要等多长时间才有人接电话,由于她听不到任何声音,实际上她在奇怪电话到底有没有接通。相对

而言,另一个程序 hold()则比较友好,它能在等待的同时播放保持音乐(MOH, Music on Hold)。

originate user/alice &hold() 当然,你也可以直接播放一个特定的声音文件:

originate user/alice &playback(/root/welcome.wav) 或者,直接录音:

以上的例子实际上都只是建立一个 Channel,相当于 FreeSWITCH 作为一个 UA 跟 alice 通话。它是个一条腿(one leg,只有a-leg)的通话。在大多数情况下,FreeSWITCH 都是做为一个 B2BUA 来桥接两个 UA 进行通话话的。在 alice 接听电话以后,bridge()程序可以再启动一个 UA 呼叫 bob:

originate user/alice &bridge(user/bob)

终于,alice 和 bob 可以通话了。我们也可以用另一个方式建立他们之音的通话:

originate user/alice &park() originate user/bob &park() show channels

uuid_bridge

在这里,我们分别呼叫 alice 和 bob,并把他们暂时 park 到一个地方。通过命令 show channels 我们可以知道每个 Channel 的 UUID,然后使用 uuid_bridge 命令将两个 Channel 桥接起来。与上一种方式不同,上一种方式实际上是先桥接,再呼叫 bob。

上面,我们一共学习了两条命令(API),originate 和 uuid_bridge。以及几个程序(APP) - echo、park、bridge等。细心的读者可以会发现,uuid_bridge API 和 bridge APP 有些类似,我也知道他们一个是先呼叫后桥接,另一个是先桥接后呼叫,那么,它们到底有什么本质的区别呢?

简单来说,一个 APP 是一个程序(Application),它作为一个 Channel 一端与另一端的 UA 进行通信,相当于它工作在 Channel 内

originate user/alice &record(/root/voice_of_alice.wav)

部;而一个 API 则是独立于一个 Channel 之外的,它只能通过 UUID 来控制一个 Channel(如果需要的话)。

这就是 API 与 APP 最本质的区别。通常,我们在控制台上输入的命令都是 API;而在 dialplan 中执行的程序都是 APP(dialplan 中也能执行一些特殊的 API)。大部分公用的 API 都是在 mod_commands 模块中加载的;而 APP 则在 mod_dptools 中,因而 APP 又称为拨号计划工具(Dialplan Tools)。某些模块(如 mod_sofia)有自己的的 API 和 APP。

某些 APP 有与其对应的 API,如上述的 bridge/uuid_bridge,还有 transfer/uuid_transfer、playback/uuid_playback等。UUID 版本的 API 都是在一个 Channel 之外对 Channel 进行控制的,它们应用于不能参与到通话中却又想对正在通话的 Channel做点什么的场景中。例如 alice 和 bob 正在畅聊,有个坏蛋使用 uuid_kill 将电话切断,或使用 uuid_broadcast 给他们广播恶作剧音频,或者使用 uuid_record 把他们谈话的内容录音等。 命令行帮助

在本章的最后,我们来学习一个如何使用 FreeSWITCH 的命令行帮助。

使用 help 命令可以列出所有命令的帮助信息。某些命令,也有自己的帮助信息,如 sofia:

**********************************>sofiahelp USAGE:

--------------------------------------------------------------------------------

sofia help

sofia profile [[start|stop|restart|rescan] [reloadxml]|flush_inbound_reg [reboot]|[register|unregister]

....

[]

其中,用尖括号(< >)括起来的表示要输入的参数,而用方括号([ ])括起来的则表示可选项,该参数可以有也可以没有。用竖线(|)分开的参数列表表示“或”的关系,即只能选其一。

FreeSWITCH 的命令参数没有统一的解析函数,而都是由命令本身的函数负责解析的,因而不是很规范,不同的命令可能有不同的风格。所以使用时,除使用帮助信息外,最好还是查阅一下 Wiki 上的帮助(),那里大部分命令都有相关的例子。关于 APP,则可以参考。本书的附录中也有相应的中文参考。 小结

本章介绍了如何启动与控制 FreeSWTICH,并提到了几个常用的命令。另外,本章还着重讲述了 APP 与 API 的区别,搞清楚这些概念对后面的学习是很有帮助的。

因篇幅问题不能全部显示,请点此查看更多更全内容