前言
Asterisk模块无论内容复杂与否,它的结构都是十分简单的。本文将说明如何新建一个res_helloworld.so模块,以及在res_helloworld.so中添加cdr显示和cli功能。
一、新建res_helloworld.so模块
创建文件名为res_helloworld.c,存放在Asterisk的源代码树/res目录下。
首先每个Asterisk模块都包含主要的Asterisk头文件,asterisk.h
#include "asterisk.h"
接下来,包含ASTERISK_FILE_VERSION宏,该宏用于注册该文件的版本,通过CLI命令“core show file version like filename”命令查看文件SVN版本。
ASTERISK_FILE_VERSION(__FILE__, "$Revision: 100001 $")
包含Asterisk模块头文件,包含该头文件是定义实现Asterisk模块所必须的。
#include "asterisk/module.h"
让我们继续进行同时包含使用Asteisk日志模块接口,用于显示Asterisk日志信息,显示日志信息也是本模块所要做的事情。
#include "asterisk/logger.h"
现在包含每个Asterisk模块必须的使用的两个函数,load_module()和unload_module().当Asterisk加载和卸载模块时会调用他们。
static int load_module(void)
{
ast_log(LOG_NOTICE, "Hello World!/n");
return AST_MODULE_LOAD_SUCCESS;
}
static int unload_module(void)
{
ast_log(LOG_NOTICE, "Goodbye World!/n");
return 0;
}
最后,每个模块必须包含AST_MODULE_INFO宏实例。该宏包含模块必要代码是用于该模块被加载时向Asterisk core注册自己。
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Hello World");
最终的结果构成res_helloworld.c文件
#include "asterisk.h" //每个Asterisk模块都包含主要的Asterisk头文件
#include "asterisk/module.h" //实现asterisk模块所必需的
#include "asterisk/logger.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision: 100001 $")//该宏用于注册该文件的版本
static int load_module(void) //Asterisk加载和卸载模块时会调用他们
{
ast_log(LOG_NOTICE, "Hello World!\n");
return AST_MODULE_LOAD_SUCCESS;
}
static int unload_module(void)
{
ast_log(LOG_NOTICE, "Goodbye World!\n");
return 0;
}
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Hello World");
重新编译Asterisk,编译系统将自动发现该模块,该模块像其他模块一样,也会被编译,最后安装。
编译成功后,将res_helloworld.so 拷贝到 /usr/lib/asterisk/modules目录下。
运行Asterisk,这时可以确认你的模块是否被正确的加载:
*CLI> module show like helloworld
Module Description Use Count
res_helloworld.so Hello World 0
1 modules loaded
通过CLI命令可以自己卸载、加载你的模块,可以观测到日志信息。
*CLI> module unload res_helloworld.so
[Jun 19 10:50:57] NOTICE[26612]: res_helloworld.c:35 unload_module: Goodbye World!
*CLI> module load res_helloworld.so
[Jun 19 10:51:05] NOTICE[26612]: res_helloworld.c:42 load_module: Hello World!
Loaded res_helloworld.so => (Hello World)
二、res_helloworld.so模块中引入cdr
第一部分已经介绍了res_helloworld.so 模块。该模块只实现了如下功能:通过Asterisk 编译后,再加载到Asteisk 中去,当被加载或卸载时打印简单的日志信息。现在让该模块做些更有意思的事情。
asterisk模块加载的两个关键函数,load_module和 unload_module,它们的具体功能如下:
load_module是:加载模块,为asterisk添加新的功能供asterisk使用。
unload_module :卸载当前模块,asterisk将无法使用相关的功能,若使用将会报错。
现在是更新模块提供功能给Asterisk 的时候了。我们将从CDR 处理开始,CDR 接口 不算是一个非常简单的应用。首先是添加合适的头文件。
#include "asterisk/cdr.h"
然后,我们增加一个新的函数,该函数在每次CDR post 时将会调用,参数是CDR 本身。
static int cdr_helloworld(struct ast_cdr *cdr)
{
ast_log(LOG_NOTICE,"We got a CDR for channel '%s'. Source: '%s', Dest: '%s', Duration: %ld/n",cdr->channel, cdr->src, cdr->dst, cdr->duration);
return 0;
}
接下来,将刚刚实现的应用通过load_module() 和unload_module() 函数添加到该模块中去。
在load_module() 函数中,将增加一个函数调用用于向Asterisk Core 注册该CDR 处理。参数是该应用的名称,简短的描述,以及当CDR post 时Asterisk Core 调用函数。
ast_cdr_register("HelloWorld", "Hello World CDR Handler", cdr_helloworld);
在unload_module() 中,需要对刚刚添加的应用注销。
ast_cdr_unregister("HelloWorld");
更新后的res_helloworld.c文件如下:
#include "asterisk.h" //每个Asterisk模块都包含主要的Asterisk头文件
#include "asterisk/module.h" //实现asterisk模块所必需的
#include "asterisk/logger.h"
#include "asterisk/cdr.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision: 100001 $")//该宏用于注册该文件的版本
static int cdr_helloworld(struct ast_cdr *cdr)
{
ast_log(LOG_NOTICE,"We got a CDR for channel '%s'. Source: '%s', Dest: '%s', Duration: %ld/n",cdr->channel, cdr->src, cdr->dst, cdr->duration);
return 0;
}
static int load_module(void) //Asterisk加载和卸载模块时会调用他们
{
//ast_log(LOG_NOTICE, "Hello World!\n");
ast_cdr_register("HelloWorld", "Hello World CDR Handler", cdr_helloworld);
return AST_MODULE_LOAD_SUCCESS;
}
static int unload_module(void)
{
//ast_log(LOG_NOTICE, "Goodbye World!\n");
ast_cdr_unregister("HelloWorld");
return 0;
}
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Hello World");
编译安装后,运行Asterisk ,可以去确认你的新CDR 应用是否被注册到了Asterisk Core 。
*CLI> cdr show status
CDR registered backend: HelloWorld
当你挂电话,将会看到CDR 处理被执行。
[Jun 20 18:08:29] NOTICE[4922]: res_helloworld.c:36 cdr_helloworld: We got a CDR for channel ‘SIP/5001-007e9da8′. Source: ‘5001′, Dest: ‘586′, Duration: 1
其中ast_cdr_register函数和ast_cdr_unregister函数是在cdr.c中定义的,通过asterisk/cdr.h调用。
三、res_helloworld.so模块引入CLI
在该部分你将看到如何实现Asterisk CLI命令。对Asterisk来说,CLI是尤为重要的,无论是在进行配置、显示状态以及调试都会用到。该部分将对第二部分的基础上进行添加代码。
首先我们需要包含定义CLI命令接口的头文件。
#include "asterisk/cli.h"
我们要实现的命令是echo,用于回显,不过只回显第一个参数。用于实现CLI回显的部分代码如下,稍后解释。
static char *handle_cli_echo(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
switch (cmd) {
case CLI_INIT:
e->command = "echo";
e->usage =
"Usage: echo <stuff>/n"
" Print back the first argument./n"
"Examples:/n"
" echo foo/n"
" echo /"multiple words/"/n"
"";
return NULL;
case CLI_GENERATE:
return NULL;
}
if (a->argc == e->args) {
ast_cli(a->fd, "You did not provide an argument to echo/n/n");
return CLI_SHOWUSAGE;
}
ast_cli(a->fd, "%s/n", a->argv[1]);
return CLI_SUCCESS;
}
首行定义的CLI处理接口符合CLI命令行处理的函数原型。ast_cli_entry包含CLI命令处理的静态信息,当CLI命令非正常执行其他调用的时候,会设置cmd参数。ast_cli_args包含参数信息。
在函数的开头switch是用来处理cmd参数,以指示调用什么功能。有两种情况需要处理:
CLI_INIT:每一个CLI命令都需要处理程序都要执行一次CLI_INIT,用来要求说明命令是什么,用法是什么等。
CLI_GENERATE:用户自动补全,相当于一个默认出口?类似default?
接下来的代码表示至少给函数提供了一个参数。值得注意的是,这里的ast_cli_entry参数用于检索多少命令本身定义的参数和ast_cli_args用于检索多少参数,实际上是在CLI命令行下执行此命令时指定的。如果它们相等,就能提供简单的"echo".
if (a->argc == e->args) {
ast_cli(a->fd, "You did not provide an argument to echo/n/n");
return CLI_SHOWUSAGE;
}
最后,我们打印一个参数到CLI,返回CLI命令成功执行的结果。
ast_cli(a->fd, "%s/n", a->argv[1]);
return CLI_SUCCESS;
下一步增加包含在这个模块里的CLI命令表。我们将使用AST_CLI_DEFINE()为表增加单个入口。AST_CLI_DEFINE包含了CLI处理命令函数的指针,以及这个命令是用来做什么的总结。
static struct ast_cli_entry cli_helloworld[] = {
AST_CLI_DEFINE(handle_cli_echo,"Echo the CLI"),
};
最后,就像我们在前面文章中提到的那样,我们要修改load_module() 和unload_module()来把CLI命令表注册到Asterisk内核中。
卸载模块unload_module,加入:
ast_cli_unregister_multiple(cli_helloworld,ARRAY_LEN(cli_helloworld));
加载模块load_module,加入:
ast_cli_register_multiple(cli_helloworld,ARRAY_LEN(cli_helloworld));
更新后的res_helloworld.c文件如下:
#include "asterisk.h" //每个Asterisk模块都包含主要的Asterisk头文件
#include "asterisk/module.h" //实现asterisk模块所必需的
#include "asterisk/logger.h"
#include "asterisk/cdr.h"
#include "asterisk/cli.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision: 100001 $")//该宏用于注册该文件的版本
static int cdr_helloworld(struct ast_cdr *cdr)
{
ast_log(LOG_NOTICE,"We got a CDR for channel '%s'. Source: '%s', Dest: '%s', Duration: %ld/n",cdr->channel, cdr->src, cdr->dst, cdr->duration);
return 0;
}
static char *handle_cli_echo(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
switch (cmd) {
case CLI_INIT:
e->command = "echo";
e->usage =
"Usage: echo <stuff>/n"
" Print back the first argument./n"
"Examples:/n"
" echo foo/n"
" echo /"multiple words/"/n"
"";
return NULL;
case CLI_GENERATE:
return NULL;
}
if (a->argc == e->args) {
ast_cli(a->fd, "You did not provide an argument to echo/n/n");
return CLI_SHOWUSAGE;
}
ast_cli(a->fd, "%s/n", a->argv[1]);
return CLI_SUCCESS;
}
static struct ast_cli_entry cli_helloworld[] = {
AST_CLI_DEFINE(handle_cli_echo,"Echo the CLI"),
};
static int load_module(void) //Asterisk加载和卸载模块时会调用他们
{
//ast_log(LOG_NOTICE, "Hello World!\n");
ast_cdr_register("HelloWorld", "Hello World CDR Handler", cdr_helloworld);
ast_cli_register_multiple(cli_helloworld,ARRAY_LEN(cli_helloworld));
return AST_MODULE_LOAD_SUCCESS;
}
static int unload_module(void)
{
//ast_log(LOG_NOTICE, "Goodbye World!\n");
ast_cdr_unregister("HelloWorld");
ast_cli_unregister_multiple(cli_helloworld,ARRAY_LEN(cli_helloworld));
return 0;
}
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Hello World");
就这样,重新编译安装这个模块,然后你可以试试这个模块了:
*CLI> help echo
Usage:echo
Print back the first argument.
Examples:
echo foo
echo "multiple words"
*CLI> echo
You did not to provide an argument to echo
Usage:echo
Print back the first argument.
Examples:
echo foo
echo "multiple words"
*CLI> echo foo
foo
*CLI> echo helloworld
helloworld
*CLI> echo "helloworld"
helloworld