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

  1. /*! /brief A command line entry */  
  2.   
  3. struct ast_cli_entry {  
  4.   
  5.         char * const cmda[AST_MAX_CMD_LEN];//命令的格式,显示字符串  
  6.   
  7.         /*! Handler for the command (fd for output, # of args, argument list). 
  8.  
  9.           Returns RESULT_SHOWUSAGE for improper arguments. 
  10.  
  11.           argv[] has argc 'useful' entries, and an additional NULL entry 
  12.  
  13.           at the end so that clients requiring NULL terminated arrays 
  14.  
  15.           can use it without need for copies. 
  16.  
  17.           You can overwrite argv or the strings it points to, but remember 
  18.  
  19.           that this memory is deallocated after the handler returns. 
  20.  
  21.          */  
  22.   
  23.         int (*handler)(int fd, int argc, char *argv[]);//命令的具体处理函数,传入参数不对时返回RESULT_SHOWUSAGE  
  24.   
  25.         /*! Summary of the command (< 60 characters) */  
  26.   
  27.         const char *summary;//命令的概述  
  28.   
  29.         /*! Detailed usage information */  
  30.   
  31.         const char *usage;//命令的详细使用范围  
  32.   
  33.         /*! Generate the n-th (starting from 0) possible completion 
  34.  
  35.           for a given 'word' following 'line' in position 'pos'. 
  36.  
  37.           'line' and 'word' must not be modified. 
  38.  
  39.           Must return a malloc'ed string with the n-th value when available, 
  40.  
  41.           or NULL if the n-th completion does not exist. 
  42.  
  43.           Typically, the function is called with increasing values for n 
  44.  
  45.           until a NULL is returned. 
  46.  
  47.          */  
  48.   
  49.         char *(*generator)(const char *line, const char *word, int pos, int n);//自动完成命令的函数,尽可能补充长的命令,按tab时。  
  50.   
  51.         struct ast_cli_entry *deprecate_cmd;//和此命令有冲突的命令  
  52.   
  53.         /*! For keeping track of usage */  
  54.   
  55.         int inuse;//函数现在的使用范围,用于追踪。  
  56.   
  57.         struct module *module;  /*! module this belongs to */ //命令属于的模块  
  58.   
  59.         char *_full_cmd;        /* built at load time from cmda[] */ //完整命令,由cmda[]连接而成  
  60.   
  61.         /* This gets set in ast_cli_register() 
  62.  
  63.           It then gets set to something different when the deprecated command 
  64.  
  65.           is run for the first time (ie; after we warn the user that it's deprecated) 
  66.  
  67.          */  
  68.   
  69.         int deprecated;  
  70.   
  71.         char *_deprecated_by;   /* copied from the "parent" _full_cmd, on deprecated commands */  
  72.   
  73.         /*! For linking */  
  74.   
  75.         AST_LIST_ENTRY(ast_cli_entry) list;  
  76.   
  77. };  
  78.   
  79.   
  80. /*! /brief A command line entry */  
  81.   
  82. struct ast_cli_entry {  
  83.   
  84.         char * const cmda[AST_MAX_CMD_LEN];//命令的格式,显示字符串  
  85.   
  86.         /*! Handler for the command (fd for output, # of args, argument list). 
  87.  
  88.           Returns RESULT_SHOWUSAGE for improper arguments. 
  89.  
  90.           argv[] has argc 'useful' entries, and an additional NULL entry 
  91.  
  92.           at the end so that clients requiring NULL terminated arrays 
  93.  
  94.           can use it without need for copies. 
  95.  
  96.           You can overwrite argv or the strings it points to, but remember 
  97.  
  98.           that this memory is deallocated after the handler returns. 
  99.  
  100.          */  
  101.   
  102.         int (*handler)(int fd, int argc, char *argv[]);//命令的具体处理函数,传入参数不对时返回RESULT_SHOWUSAGE  
  103.   
  104.         /*! Summary of the command (< 60 characters) */  
  105.   
  106.         const char *summary;//命令的概述  
  107.   
  108.         /*! Detailed usage information */  
  109.   
  110.         const char *usage;//命令的详细使用范围  
  111.   
  112.         /*! Generate the n-th (starting from 0) possible completion 
  113.  
  114.           for a given 'word' following 'line' in position 'pos'. 
  115.  
  116.           'line' and 'word' must not be modified. 
  117.  
  118.           Must return a malloc'ed string with the n-th value when available, 
  119.  
  120.           or NULL if the n-th completion does not exist. 
  121.  
  122.           Typically, the function is called with increasing values for n 
  123.  
  124.           until a NULL is returned. 
  125.  
  126.          */  
  127.   
  128.         char *(*generator)(const char *line, const char *word, int pos, int n);//自动完成命令的函数,尽可能补充长的命令,按tab时。  
  129.   
  130.         struct ast_cli_entry *deprecate_cmd;//和此命令有冲突的命令  
  131.   
  132.         /*! For keeping track of usage */  
  133.   
  134.         int inuse;//函数现在的使用范围,用于追踪。  
  135.   
  136.         struct module *module;  /*! module this belongs to */ //命令属于的模块  
  137.   
  138.         char *_full_cmd;        /* built at load time from cmda[] */ //完整命令,由cmda[]连接而成  
  139.   
  140.         /* This gets set in ast_cli_register() 
  141.  
  142.           It then gets set to something different when the deprecated command 
  143.  
  144.           is run for the first time (ie; after we warn the user that it's deprecated) 
  145.  
  146.          */  
  147.   
  148.         int deprecated;  
  149.   
  150.         char *_deprecated_by;   /* copied from the "parent" _full_cmd, on deprecated commands */  
  151.   
  152.         /*! For linking */  
  153.   
  154.         AST_LIST_ENTRY(ast_cli_entry) list;  
  155.   
  156. };  

具体ast_cli_entry例子

具体例子: chan_sip.c

  1. static struct ast_cli_entry cli_sip[] = {  
  2.   
  3.         { { "sip", "show", "channels", NULL },  
  4.   
  5.         sip_show_channels, "List active SIP channels",  
  6.   
  7.         show_channels_usage },  
  8.   
  9.    
  10.   
  11.         { { "sip", "show", "domains", NULL },  
  12.   
  13.         sip_show_domains, "List our local SIP domains.",  
  14.   
  15.         show_domains_usage },  
  16.   
  17.         ...  
  18.   
  19.         { { "sip", "show", "users", NULL },  
  20.   
  21.         sip_show_users, "List defined SIP users",  
  22.   
  23.         show_users_usage },  
  24.   
  25.         ...  

模块中如何使用见 三.外围模块中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,显示使用范围。具体代码:

  1. /*! /brief  CLI Command 'SIP Show Users' */  
  2.   
  3. static int sip_show_users(int fd, int argc, char *argv[])  
  4.   
  5. {  
  6.   
  7.         regex_t regexbuf;  
  8.   
  9.         int havepattern = FALSE;  
  10.   
  11.    
  12.   
  13. #define FORMAT  "%-25.25s  %-15.15s  %-15.15s  %-15.15s  %-5.5s%-10.10s/n"  
  14.   
  15.    
  16.   
  17.         switch (argc) {  
  18.   
  19.         case 5:  
  20.   
  21.                 if (!strcasecmp(argv[3], "like")) {  
  22.   
  23.                         if (regcomp(®exbuf, argv[4], REG_EXTENDED | REG_NOSUB))  
  24.   
  25.                                 return RESULT_SHOWUSAGE;  
  26.   
  27.                         havepattern = TRUE;  
  28.   
  29.                 } else  
  30.   
  31.                         return RESULT_SHOWUSAGE;  
  32.   
  33.         case 3:  
  34.   
  35.                 break;  
  36.   
  37.         default:  
  38.   
  39.                 return RESULT_SHOWUSAGE;  
  40.   
  41.         }  
  42.   
  43.    
  44.   
  45.         ast_cli(fd, FORMAT, "Username", "Secret", "Accountcode", "Def.Context", "ACL", "NAT");  
  46.   
  47.         ASTOBJ_CONTAINER_TRAVERSE(&userl, 1, do {  
  48.   
  49.                 ASTOBJ_RDLOCK(iterator);  
  50.   
  51.    
  52.   
  53.                 if (havepattern && regexec(®exbuf, iterator->name, 0, NULL, 0)) {  
  54.   
  55.                         ASTOBJ_UNLOCK(iterator);  
  56.   
  57.                         continue;  
  58.   
  59.                 }  
  60.   
  61.    
  62.   
  63.                 ast_cli(fd, FORMAT, iterator->name,   
  64.   
  65.                         iterator->secret,   
  66.   
  67.                         iterator->accountcode,  
  68.   
  69.                         iterator->context,  
  70.   
  71.                         iterator->ha ? "Yes" : "No",  
  72.   
  73.                         nat2str(ast_test_flag(&iterator->flags[0], SIP_NAT)));  
  74.   
  75.                 ASTOBJ_UNLOCK(iterator);  
  76.   
  77.         } while (0)  
  78.   
  79.         );  
  80.   
  81.    
  82.   
  83.         if (havepattern)  
  84.   
  85.                 regfree(®exbuf);  
  86.   
  87.    
  88.   
  89.         return RESULT_SUCCESS;  
  90.   
  91. #undef FORMAT  
  92.   
  93. }  

一.核心文件中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执行命令,源码
  1. int ast_cli_command_multiple(int fd, size_t size, const char *s)  
  2.   
  3. {  
  4.   
  5.         char cmd[512];  
  6.   
  7.         int x, y = 0, count = 0;  
  8.   
  9.    
  10.   
  11.         for (x = 0; x < size; x++) {  
  12.   
  13.                 cmd[y] = s[x];  
  14.   
  15.                 y++;  
  16.   
  17.                 if (s[x] == '/0') {  
  18.   
  19.                         ast_cli_command(fd, cmd);  
  20.   
  21.                         y = 0;  
  22.   
  23.                         count++;  
  24.   
  25.                 }  
  26.   
  27.         }  
  28.   
  29.         return count;  
  30.   
  31. }  

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。
  1. static void *listener(void *unused)  
  2.   
  3. {  
  4.   
  5.   ...  
  6.   
  7.         for (;;) {  
  8.   
  9.          ...  
  10.   
  11.                 s = poll(fds, 1, -1);  
  12.   
  13.                 if (s < 0) {  
  14.   
  15.                         if (errno != EINTR)  
  16.   
  17.                                 ast_log(LOG_WARNING, "Accept returned %d: %s/n", s, strerror(errno));  
  18.   
  19.                 } else {  
  20.   
  21.                         for (x = 0; x < AST_MAX_CONNECTS; x++) {  
  22.   
  23.                                 if (consoles[x].fd < 0) {  
  24.   
  25.                                         ...  
  26.   
  27.                                         if (ast_pthread_create_background(&consoles[x].t, &attr, netconsole, &consoles[x])) {  
  28.   
  29.                                                 ast_log(LOG_ERROR, "Unable to spawn thread to handle connection: %s/n", strerror(errno));  
  30.   
  31.                                                 close(consoles[x].p[0]);  
  32.   
  33.                                                 close(consoles[x].p[1]);  
  34.   
  35.                                                 consoles[x].fd = -1;  
  36.   
  37.                                                 fdprint(s, "Server failed to spawn thread/n");  
  38.   
  39.                                                 close(s);  
  40.   
  41.                                         }  
  42.   
  43.                                  ...  
  44.   
  45.                                 }  
  46.   
  47.                           ...  
  48.   
  49.                           }  
  50.   
  51.                    }  
  52.   
  53.             }  
  54.   
  55.   ...  
  56.   
  57. }  

它又被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。如:

 

  1. static int load_module(void)  
  2.   
  3. {  
  4.   
  5.         ...   
  6.   
  7.         /* Register all CLI functions for SIP */  
  8.   
  9.         ast_cli_register_multiple(cli_sip, sizeof(cli_sip)/ sizeof(struct ast_cli_entry));  
  10.   
  11.         ...  
  12.   
  13. }  

 

 四.参照其思想简化实现

 

    在实现中根据自身的需求和时间决定暂时先简化实现一个cli,主要需要其动态注册功能,但是对于其中一些具体实现进行简化,如命令cmd暂时只接受一个字符串,只使用最快查找等。但对于开发接口都尽量保留,以便后期开发。对于其运用到其他运行支撑模块如lock.h,linkedlist.h 等进行实现,但对于io模块暂时未实现。

    测试代码,功能达到要求。

原文地址:https://www.cnblogs.com/einyboy/p/2745907.html