黑龙江包括哪些城市:Asterisk cli模块分析

来源:百度文库 编辑:九乡新闻网 时间:2024/04/20 05:26:40
最近写一些工具库,需要远程命令行调试(cli)功能,原有的一个cli模块是将接收处理的命令具体实现在cli模块中,其他模块需要修改添加自己的cli命令都需要去修改cli模块代码,觉得模块间耦合度太高,在看asterisk源码时记得它的cli模块是一种注册机制,cli模块主要对外提供注册和反注册接口,其他模块实现一组特定的cli entry,再调用注册和反注册函数进行操作。可以动态的控制远程可操作的cli命令,觉得比较好,分析了一下。并参照它的思想简化的实现了一个满足自己需求的cli模块。

以下文章原是写在trac的wiki与代码结合使用的,所以部分超链接在此网页中无法使用。

Asterisk cli模块分析


整体流程分析图

关键结构体ast_cli_entry:include/asterisk/cli.h

view plaincopy to clipboardprint?

  1. /*! /brief A command line entry */
  2. 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中

    view plaincopy to clipboardprint?

    1. static struct ast_cli_entry cli_sip[] = {
    2. { { "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,显示使用范围。具体代码:

    view plaincopy to clipboardprint?

    1. /*! /brief CLI Command 'SIP Show Users' */
    2. 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执行命令,源码

    view plaincopy to clipboardprint?

    1. int ast_cli_command_multiple(int fd, size_t size, const char *s)
    2. {
    3. 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。

    view plaincopy to clipboardprint?

    1. static void *listener(void *unused)
    2. {
    3. ...
    4. 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。如:

    view plaincopy to clipboardprint?

    1. static int load_module(void)
    2. {
    3. ...
    4. /* 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模块暂时未实现。

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