Asterisk cli模块分析
最近写一些工具库,需要远程命令行调试(cli)功能,原有的一个cli模块是将接收处理的命令具体实现在cli模块中,其他模块需要修改添加自己的cli命令都需要去修改cli模块代码,觉得模块间耦合度太高,在看asterisk源码时记得它的cli模块是一种注册机制,cli模块主要对外提供注册和反注册接口,其他模块实现一组特定的cli entry,再调用注册和反注册函数进行操作。可以动态的控制远程可操作的cli命令,觉得比较好,分析了一下。并参照它的思想简化的实现了一个满足自己需求的cli模块。
以下文章原是写在trac的wiki与代码结合使用的,所以部分超链接在此网页中无法使用。
Asterisk cli模块分析
整体流程分析图
关键结构体ast_cli_entry:include/asterisk/cli.h
- /*! /brief A command line entry */
- struct ast_cli_entry {
- char * const cmda[AST_MAX_CMD_LEN];//命令的格式,显示字符串
- /*! Handler for the command (fd for output, # of args, argument list).
- Returns RESULT_SHOWUSAGE for improper arguments.
- argv[] has argc 'useful' entries, and an additional NULL entry
- at the end so that clients requiring NULL terminated arrays
- can use it without need for copies.
- You can overwrite argv or the strings it points to, but remember
- that this memory is deallocated after the handler returns.
- */
- int (*handler)(int fd, int argc, char *argv[]);//命令的具体处理函数,传入参数不对时返回RESULT_SHOWUSAGE
- /*! Summary of the command (< 60 characters) */
- const char *summary;//命令的概述
- /*! Detailed usage information */
- const char *usage;//命令的详细使用范围
- /*! Generate the n-th (starting from 0) possible completion
- for a given 'word' following 'line' in position 'pos'.
- 'line' and 'word' must not be modified.
- Must return a malloc'ed string with the n-th value when available,
- or NULL if the n-th completion does not exist.
- Typically, the function is called with increasing values for n
- until a NULL is returned.
- */
- char *(*generator)(const char *line, const char *word, int pos, int n);//自动完成命令的函数,尽可能补充长的命令,按tab时。
- struct ast_cli_entry *deprecate_cmd;//和此命令有冲突的命令
- /*! For keeping track of usage */
- int inuse;//函数现在的使用范围,用于追踪。
- struct module *module; /*! module this belongs to */ //命令属于的模块
- char *_full_cmd; /* built at load time from cmda[] */ //完整命令,由cmda[]连接而成
- /* This gets set in ast_cli_register()
- It then gets set to something different when the deprecated command
- is run for the first time (ie; after we warn the user that it's deprecated)
- */
- int deprecated;
- char *_deprecated_by; /* copied from the "parent" _full_cmd, on deprecated commands */
- /*! For linking */
- AST_LIST_ENTRY(ast_cli_entry) list;
- };
- /*! /brief A command line entry */
- struct ast_cli_entry {
- char * const cmda[AST_MAX_CMD_LEN];//命令的格式,显示字符串
- /*! Handler for the command (fd for output, # of args, argument list).
- Returns RESULT_SHOWUSAGE for improper arguments.
- argv[] has argc 'useful' entries, and an additional NULL entry
- at the end so that clients requiring NULL terminated arrays
- can use it without need for copies.
- You can overwrite argv or the strings it points to, but remember
- that this memory is deallocated after the handler returns.
- */
- int (*handler)(int fd, int argc, char *argv[]);//命令的具体处理函数,传入参数不对时返回RESULT_SHOWUSAGE
- /*! Summary of the command (< 60 characters) */
- const char *summary;//命令的概述
- /*! Detailed usage information */
- const char *usage;//命令的详细使用范围
- /*! Generate the n-th (starting from 0) possible completion
- for a given 'word' following 'line' in position 'pos'.
- 'line' and 'word' must not be modified.
- Must return a malloc'ed string with the n-th value when available,
- or NULL if the n-th completion does not exist.
- Typically, the function is called with increasing values for n
- until a NULL is returned.
- */
- char *(*generator)(const char *line, const char *word, int pos, int n);//自动完成命令的函数,尽可能补充长的命令,按tab时。
- struct ast_cli_entry *deprecate_cmd;//和此命令有冲突的命令
- /*! For keeping track of usage */
- int inuse;//函数现在的使用范围,用于追踪。
- struct module *module; /*! module this belongs to */ //命令属于的模块
- char *_full_cmd; /* built at load time from cmda[] */ //完整命令,由cmda[]连接而成
- /* This gets set in ast_cli_register()
- It then gets set to something different when the deprecated command
- is run for the first time (ie; after we warn the user that it's deprecated)
- */
- int deprecated;
- char *_deprecated_by; /* copied from the "parent" _full_cmd, on deprecated commands */
- /*! For linking */
- AST_LIST_ENTRY(ast_cli_entry) list;
- };
具体ast_cli_entry例子
具体例子: chan_sip.c中
- static struct ast_cli_entry cli_sip[] = {
- { { "sip", "show", "channels", NULL },
- sip_show_channels, "List active SIP channels",
- show_channels_usage },
- { { "sip", "show", "domains", NULL },
- sip_show_domains, "List our local SIP domains.",
- show_domains_usage },
- ...
- { { "sip", "show", "users", NULL },
- sip_show_users, "List defined SIP users",
- show_users_usage },
- ...
模块中如何使用见 三.外围模块中cli机制
cli_sip是sip通道模块注册的cli命令集合。其中每一个是具体的一个命令。以"sip show users"为例,{ "sip", "show", "users", NULL }是cmda,sip_show_users是handler(函数),"List defined SIP users"是命令概述,show_users_usage是命令使用范围(一个字符串)。
以sip_show_users分析handler的结构,位于: chan_sip.c,成功返回RESULT_SUCCESS,失败返回RESULT_SHOWUSAGE,显示使用范围。具体代码:
- /*! /brief CLI Command 'SIP Show Users' */
- static int sip_show_users(int fd, int argc, char *argv[])
- {
- regex_t regexbuf;
- int havepattern = FALSE;
- #define FORMAT "%-25.25s %-15.15s %-15.15s %-15.15s %-5.5s%-10.10s/n"
- switch (argc) {
- case 5:
- if (!strcasecmp(argv[3], "like")) {
- if (regcomp(®exbuf, argv[4], REG_EXTENDED | REG_NOSUB))
- return RESULT_SHOWUSAGE;
- havepattern = TRUE;
- } else
- return RESULT_SHOWUSAGE;
- case 3:
- break;
- default:
- return RESULT_SHOWUSAGE;
- }
- ast_cli(fd, FORMAT, "Username", "Secret", "Accountcode", "Def.Context", "ACL", "NAT");
- ASTOBJ_CONTAINER_TRAVERSE(&userl, 1, do {
- ASTOBJ_RDLOCK(iterator);
- if (havepattern && regexec(®exbuf, iterator->name, 0, NULL, 0)) {
- ASTOBJ_UNLOCK(iterator);
- continue;
- }
- ast_cli(fd, FORMAT, iterator->name,
- iterator->secret,
- iterator->accountcode,
- iterator->context,
- iterator->ha ? "Yes" : "No",
- nat2str(ast_test_flag(&iterator->flags[0], SIP_NAT)));
- ASTOBJ_UNLOCK(iterator);
- } while (0)
- );
- if (havepattern)
- regfree(®exbuf);
- return RESULT_SUCCESS;
- #undef FORMAT
- }
一.核心文件中cli机制,main函数调用cli流程
1.cli.c和cli.h分析
- 1. ast_cli:用于在cli界面上显示信息。 源码
void ast_cli(int fd, char *fmt, ...)
fd为文件句柄,fmt和后面的参数是要显示的信息。
- 2. ast_cli_command:用于具体处理cli上输入的命令,选择对应的handler处理。源码
int ast_cli_command(int fd, const char *s)
fd为文件句柄,s是arg,但是s是将所有argv拼起来的一个字符串,在函数内部调用 parse_args将其分离还原。
- 3. ast_cli_command_multiple循环调用 ast_cli_command执行命令,源码
- int ast_cli_command_multiple(int fd, size_t size, const char *s)
- {
- char cmd[512];
- int x, y = 0, count = 0;
- for (x = 0; x < size; x++) {
- cmd[y] = s[x];
- y++;
- if (s[x] == '/0') {
- ast_cli_command(fd, cmd);
- y = 0;
- count++;
- }
- }
- return count;
- }
2.asterisk.c分析
- 4. netconsole,在main/asterisk.c中,它为线程函数,调用了ast_cli_command_multiple。
static void *netconsole(void *vconsole)
vconsole是一个struct console结构,是一个console的结构,
ast_cli_command_multiple(con->fd, res, tmp);
res是要处理的命令的数目,tmp是命令字符串(多条命令在一起,以'/0'分隔)。 它结构中的fd即为传递给最终ast_cli的fd。
- 5. listener,它调用了netconsole,在main/asterisk.c中,为线程函数,按照consoles的数目,启动多个netconsole的线程,传递参数为console。
- static void *listener(void *unused)
- {
- ...
- for (;;) {
- ...
- s = poll(fds, 1, -1);
- if (s < 0) {
- if (errno != EINTR)
- ast_log(LOG_WARNING, "Accept returned %d: %s/n", s, strerror(errno));
- } else {
- for (x = 0; x < AST_MAX_CONNECTS; x++) {
- if (consoles[x].fd < 0) {
- ...
- if (ast_pthread_create_background(&consoles[x].t, &attr, netconsole, &consoles[x])) {
- ast_log(LOG_ERROR, "Unable to spawn thread to handle connection: %s/n", strerror(errno));
- close(consoles[x].p[0]);
- close(consoles[x].p[1]);
- consoles[x].fd = -1;
- fdprint(s, "Server failed to spawn thread/n");
- close(s);
- }
- ...
- }
- ...
- }
- }
- }
- ...
- }
- 6.ast_makesocket,在main/asterisk.c中,它启动了 listener线程。
它又被main函数调用。
二.核心文件中cli机制,向外提供的cli注册接口
- 1.ast_cli_register和__ast_cli_register。!__ast_cli_register源码和ast_cli_register源码。 不知为什么有ast_cli_register调用__ast_cli_register这一层,未看出有什么作用,而且在unregister中没有这样一层调用。__ast_cli_register具体处理将一个struct ast_cli_entry *e注册到helpers上,它调用find_cli寻找是否已经注册。没有注册使用 AST_LIST_INSERT_TAIL(&helpers, e, list)或者AST_LIST_INSERT_BEFORE_CURRENT(&helpers, e, list)将其注册到helpers上。
- 2.ast_cli_unregister。反注册一个struct ast_cli_entry *e。
- 3.find_cli,在链表buildins和helpers上寻找一个符合条件的struct ast_cli_entry *e。具体调用cli_next遍历这两个链表。在这里还有匹配度的选择,可以进行最优匹配或者最先匹配。
三.外围模块中cli机制
外围模块需要使用cli机制:
- 1.首先声明一个struct ast_cli_entry cli_sip[],将自己具有的可cli调用的函数写在其中,包括相应的说明等,如#具体ast_cli_entry例子。
- 2.在load_module中调用ast_cli_register_multiple注册cli_sip[]到helpers。如:
- static int load_module(void)
- {
- ...
- /* Register all CLI functions for SIP */
- ast_cli_register_multiple(cli_sip, sizeof(cli_sip)/ sizeof(struct ast_cli_entry));
- ...
- }
四.参照其思想简化实现
在实现中根据自身的需求和时间决定暂时先简化实现一个cli,主要需要其动态注册功能,但是对于其中一些具体实现进行简化,如命令cmd暂时只接受一个字符串,只使用最快查找等。但对于开发接口都尽量保留,以便后期开发。对于其运用到其他运行支撑模块如lock.h,linkedlist.h 等进行实现,但对于io模块暂时未实现。
测试代码,功能达到要求。