http://blog.youkuaiyun.com/ren911/category/683751.aspx
最近写一些工具库,需要远程命令行调试(cli)功能,原有的一个cli模块是将 接收处理的命令具体实现在cli模块中,其他模块需要修改添加自己的cli命令都需要去修改cli模块代码,觉得模块间耦合度太高,在看asterisk 源码时记得它的cli模块是一种注册机制,cli模块主要对外提供注册和反注册接口,其他模块实现一组特定的cli entry,再调用注册和反注册函数进行操作。可以动态的控制远程可操作的cli命令,觉得比较好,分析了一下。并参照它的思想简化的实现了一个满足自己 需求的cli模块。
以下文章原是写在trac的wiki与代码结合使用的,所以部分超链接在此网页中无法使用。
整体流程分析图
关键结构体 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模块暂时未实现。
测试代码,功能达到要求。