|
|
December 26 自动换车位、自动贴条、自动发起比赛、自动启动车队、自动加油 如果需要特定功能,或者需要按特定顺序移动汽车的脚本,可以联系我。 ------------------------------------------------------------------------------------------------------- 09.03.18 Linux版本加入参数-t明确开启自动贴条功能,默认关闭此功能, -t只有在加了-p参数时才有效。 09.01.15 参加别人发起的比赛时,自动启动车队失效, 现已更正此问题,请重新下载脚本或exe文件。 09.01.09 windows版本可用,exe可执行文件,直接双击运行,截图见下图。 Linux版本更稳定,启动车队时间改成每天中午12点后,请重新下载。 如需通过参数设置每天启动车队的时间,请修改源码或我联系。 09.01.06 修正一个线程异常的bug。 09.01.05 发现一个不能自动发起比赛的bug! 原来不加-L就不能自动启动车队,现修正为自动启动车队与-L参数无关。 =================================================================== 此外挂为ruby编写, 有windows和linux两个版本,windows下可直接双击运行,截图如下: 在linux下使用时,需要安装ruby和spidermonkey-bin。 linux下使用方法:ruby park.rb username passwd [options] -p参数表示开启自动停车,每隔50-60分钟左右检查一次,如果发现停车费超过5000则自动换车位 优先换到收费车位,如果只剩免费车位了,则停到免费车位。 自动贴条,自己的停车场中有车费达到7200的则自动贴条。 注意:是将近小时检查一次,所以如果发现达到条件没有生效,请耐心等待。 可在一次比赛结束后自动发起一场新的比赛,-L 2:2参数表示自动发起赛道2的比赛,也就是全国拉力赛,最大参赛人数为2,并且在每天中午12点之后会自动启动比赛,此时间可在源码中自行修改。 可自动为好友或主账号加油,当把鼠标放在好友名字上时,浏览器左下角出现的javascript:gotouser(xxxxx)里的参数就是用户的id,在运行脚本时指定参数-j xxxxx则可自动为好友加油,一般在每天0点到1点之间就可以完成加油。 如果你的马甲只是用来给主账号加油,而不想占用宝贵的车位,那么不需要加-p参数。 Linux用户可使用bash脚本来启动主帐号和多个马甲。 Linux下的使用方法: voldemort@WangZhong:~$ ruby park.rb -h Usage: ruby park.rb username passwd [options] example: ruby park.rb a@b.c 1234 -p -L 2:2 -j 7093181 -p, --parking park your cars to friends' parking automaticly -t, –-tietiao tie tiao when someone reaches 7200 -L, --launch [Line]:[Maxnum] launch a match automaticly, Line:1-6 Maxnum:2-3 Line 1: Dakar Rally (8421 km) Line 2: National Rally Championship (3506 km) Line 3: World Rally Championship (18734 km) Line 4: Taklimakan Desert Rally (3770 km) Line 5: Chinese police Rally (3849 km) Line 6: China-ASEAN International Rally (8475 km) -j, --jiayou [UserID] Jia-you for [UserID]'s team automaticly -h, –help display this help and exit 可以用一个bash来一次启动,假设主帐号为 a@x.x,主帐号id为9999999,其余马甲都用来给主帐号加油,则startgame.sh文件内容: #!/bin/sh ruby park.rb a@x.x 12345 -p –t -L 2:2 & sleep 5 ruby park.rb b@x.x 12345 -j 9999999 & ruby park.rb c@x.x 12345 -j 9999999 & ruby park.rb d@x.x 12345 -j 9999999 & ruby park.rb e@x.x 12345 -j 9999999 & 保存文件,chmod +x startgame.sh,然后./startgame.sh Linux版外挂下载地址: June 10 “如果你知道你在干什么,三层足够;如果你不知道你在干什么,十七层也没用。” ---Michael Padlipsky
asterisk里提供了大量的app用于在extension.conf中编写dialplan,这些app非常丰富,你几乎可以做任何事情,你甚至可以在dialplan中设置变量,使用一些简单的流程控制等。但是当你发现你需要的app在asterisk里并没有提供,而老板又天天催你交差的时候,你就不得不自己来实现它,建议大家把自己实现的app放在一个新的模块里,比如我们可以建立一个新的名为app_myapps.so的模块,那么这就又牵涉到如何添加一个新的模块。 首先,除了一些内建的app以外,所有的app都是在模块中定义的,当这个模块载入后,模块中定义的app会注册到asterisk中,这些app被按照字母序放在一个链表中,asterisk中习惯用以下宏初始化一个全局链表: #define AST_LIST_HEAD_STATIC(name, type) \ struct name { \ struct type *first; \ struct type *last; \ ast_mutex_t lock; \ } name; 比如初始化apps链表:static AST_LIST_HEAD_STATIC(apps, ast_app); 那么内建的app在哪里定义的?main/pbx.c 在这个文件中定义了很多内建命令,比如answer, hangup, busy, backgroud等,在load_pbx()函数中被注册,正是这些在pbx中定义的built-in使得asterisk可以同时使用各种协议(sip, h323, iax2, zaptel, gtalk等),成为一个类似协议转换器的东西,而且也有公司确实就拿blackfin下移植的asterisk做了pstn网关产品,asterisk具有分层的结构,在pbx层处理一些通用的共性的操作,然后根据channel的不同,调用相关的回调函数,来实现对不同协议的处理。现在以answer为例来说明一下: 假设context如下: exten => s, 1, Answer() exten => s, 2, Echo() 当有呼叫进入时,在do_monitor线程中sipsock_read获得Invite消息,然后handle_request函数调用handle_request_invite函数处理这个Invite消息,如果这是一个新的dialog,那么调用sip_new函数会返回一个ast_channel结构体(今后会详细介绍chan_sip.c,现在看不明白也没有关系),然后调用ast_pbx_start,在pbx_thread线程中调用__ast_pbx_run,然后调用ast_spawn_extension,再调用pbx_extension_help,在这个函数中,会调用pbx_findapp()来根据extension中的app名查找对应的处理函数,在这个例子中,会找到pbx_builtin_answer,这个函数的第一个参数是一个channel结构体,这里此ast_channel是一个sip channel,那么它调用的answer函数其实就是chan_sip.so中定义的sip_answer()函数,现在大家明白了吧,对于pbx层来说,他根本就不需要知道是哪个channel,这就是分层的好处。 好了,现在言归正传,一个模块文件要放在apps目录下,并用app_作为文件名的前缀,这里我们在apps目录下建立一个名为app_myapps.c的新文件。其实添加一个新的模块和app非常的简单,那么一个模块需要哪些必备的要素呢?一般来说load_module()和unload_module()这两个函数都是必须的,load_module()在载入这个模块时会被调用,所以我们要在这个函数里对app进行注册;在unload_module()时我们需要注销这个app并强制挂断所有正在使用这个模块的用户。那么下面就给出一个创建模块和新的app的代码模版吧: #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 1.1 $") //这里是asterisk core用来控制源码文件的版本的 #include <stdlib.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <ctype.h> #include <stdio.h> #include <asterisk/lock.h> #include <asterisk/file.h> #include <asterisk/logger.h> #include <asterisk/channel.h> #include <asterisk/pbx.h> #include <asterisk/options.h> #include <asterisk/config.h> #include <asterisk/module.h> #include <asterisk/enum.h> #include <asterisk/utils.h> #include <asterisk/app.h> //to add any include files you need here
static char *tdesc = "This is my app :)"; static char *app = "myapp"; static char *synopsis = "myapp(param1|param2):\n" "This Application will do something, urr?\n";
static int myapp_exec(struct ast_channel *chan, void *data) { int res=0; char *parse; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(param1); AST_APP_ARG(param2); ); struct ast_module_user *u; if (ast_strlen_zero(data)) { ast_log(LOG_WARNING, "MYAPP requires an argument (param1|param2)\n"); return -1; } u = ast_module_user_add(chan); parse = ast_strdupa(data); AST_STANDARD_APP_ARGS(args, parse); if (ast_strlen_zero(args.param1) || ast_strlen_zero(args.param2)) { ast_log(LOG_WARNING, "MYAPP requires an argument (callee|filename)\n"); res = -1; goto out1; } /* * to do anything you want to here, params are args.param1 and args.param2. */ out1: ast_module_user_remove(u); return res; }
static int load_module(void) { return ast_register_application(app, myapp_exec, synopsis, tdesc); } static int unload_module(void) { int res; res = ast_unregister_application(app); ast_module_user_hangup_all(); return res; } AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "MY OWN APPS"); //EOF
好了,一个名叫myapp的extension应用就写好了,你可以给他传两个参数,参数之间用|隔开,如果你没有修改modules.conf的话,编译后重新启动一次asterisk会自动加载app_myapps.so这个模块的。 还等什么?现在你可以为所欲为了。 June 07 相信做VoIP的朋友没有不知道Asterisk和SER的,一个是B2BUA,一个是proxy,后来从SER走出了OpenSER,这两个开源软件足以构建一套强大的VoIP的系统,于是乎一夜之间,无数做VoIP的小企业们诞生了,成为这个新兴的通信市场的开拓者。开拓者一般有两种下场,活的很爽和死的很惨,在VoIP这个领域,似乎后者居多,真是长江后浪推前浪,前浪死在沙滩上,对此也许我们只能安慰自己说,VoIP的前途是无比光明的,让我们的鬼火照亮继续前行的后继者吧。好了,咱暂且不谈这个什么叫VoIP的冬冬有没有钱途,asterisk可是个好东东,捣鼓过trixbox么?下一个玩玩吧,你会发现,VoIP没有那么遥远,也不像九阴真经一样深不可测。但是她实在是个很庞大很复杂的东西,于是在一个雷鸣交加的夜晚(烘托气氛),笔者决定今后在阅读过代码后能够留下一点人类的痕迹,当然也许并不只是代码,还包括一些Asterisk的配置技巧,extensions.conf的编写等等相关内容,如果想要深入理解asterisk中SIP相关的内容,你至少需要阅读:电话未来之路,RFC3261,K&R C books. 事实上asterisk支持很多协议,但是笔者只讨论SIP,由于水平有限(相当的),错误在所难免,希望各位不吝赐教。好吧,废话少说,看看Asterisk是怎么开始工作的吧。 ************************************************************** “我的征途是星辰大海”——金发的年轻霸主说。 “在历史长流里,一个人渺小如沧海一粟,在通往未來的无数条路上只能其一”——黑发的红茶司令官说。 ---《银河英雄传说》
ASTERISK 1.4.11 - asterisk.c - main(): 像其他程序一样,我们从main函数开始: 首先保存命令行参数(argv[]->_argv[]),以便程序重启时使用; 判断命令是否是rasterisk,如果是,设置AST_OPT_FLAG_NO_FORK和AST_OPT_FLAG_REMOTE标志位; 得到当前主机名gethostname(hostname, sizeof(hostname)-1) gethostname其实是调用了uname(2)函数来获得utsname结构体中nodename的内容(没什么用,打印启动信息,无视吧,不要和sip register里的hostname搞混了) 得到进程标识符(ast_mainpid = getpid()); 建立mu-law和a-law转换表(ast_ulaw_init(), ast_alaw_init()); 为FFT逆变换做一些初始化(callerid_init()),用于在zaptel里进行callerid的DTMF检测; 初始化内建(builtin)命令的_full_cmd字符串; 命令存放在ast_cli_entry结构体的char* const cmda[]指针数组中,将每个数组成员中的字符串组合起来放在_full_cmd中,以NULL结束。 void ast_builtins_init(void) ----- main/cli.c { struct ast_cli_entry *e; for (e = builtins; e->cmda[0] != NULL; e++) { char buf[80]; ast_join(buf, sizeof(buf), e->cmda); e->_full_cmd = strdup(buf); ...... ast_cli_register_multiple(cli_cli, sizeof(cli_cli) / sizeof(struct ast_cli_entry)); } 目前只有三个内建命令,以第一个为例,cmda[0]="_command", cmda[1]="complete", cmda[2]=NULL,注意,NULL是必须的,标志着命令名的结束。这个命令调用handle_commandcomplete执行,summary说明此命令用于命令补全,commandcomplete_help中说明此命令用于内部使用,永远不能被用户直接调用。 static struct ast_cli_entry builtins[] = { { { "_command", "complete", NULL }, handle_commandcomplete, "Command complete", commandcomplete_help }, { { "_command", "nummatches", NULL }, handle_commandnummatches, "Returns number of command matches", commandnummatches_help }, { { "_command", "matchesarray", NULL }, handle_commandmatchesarray, "Returns command matches array", commandmatchesarray_help }, { { NULL }, NULL, NULL, NULL } }; ast_cli_register_multiple(cli_cli, ...)是用来注册asterisk基本命令的,函数的实现很简单,就是从指定的入口开始加载指定数量的指令。 void ast_cli_register_multiple(struct ast_cli_entry *e, int len) {cfg = ast_config_load(AST_MODULE_CONFIG) int i; for (i = 0; i < len; i++) ast_cli_register(e + i); } 这里加载的有:no debug channel, core show channels, core show channel, core set debug channel, core set debug, core set debug off, core set verbose, group show channels, help, logger mute, module show, modules show like, module load, module reload, module unload, core show uptime, soft hangup。 注册命令的函数调用关系如下: ast_builtins_init() -> ast_cli_register_multiple() -> ast_cli_register() -> __ast_cli_register() 在__ast_cli_register()函数中,根据字母序将命令插入helpers这个外部命令列表,在asterisk中一共有两种命令,一种就是刚刚提到的builtins,一种就是helpers。 __ast_cli_register(A, B)有两个参数,表示B命令将使A命令过时,则函数中: A->deprecated=1; A->summary=B->summary; A->usage=B->usage; e->_deprecated_by = S_OR(ed->_deprecated_by, ed->_full_cmd); 最后一行的意思是:如果B令A过时,C令B过时,则告诉用户C令A过时;如果想总是显示哪个命令直接使A过时的话,只需要令S_OR的两个参数都是ed->_full_cmd即可。 最后,如果A是用来使另一个命令过时的话,则那个命令也要被注册: if (e->deprecate_cmd) { __ast_cli_register(e->deprecate_cmd, e); } 下面再回到main: 初始化base64转换(ast_utils_init()); tty/tdd初始化(tdd_init()); 查看当前用户环境变量,确定用户权限: if (getenv("ASTERISK_ALREADY_NONROOT")) is_child_of_nonroot=1; 给filename[]变量赋值为.asterisk_history文件的路径,此文件用来记录用户输入的命令: if (getenv("HOME")) snprintf(filename, sizeof(filename), "%s/.asterisk_history", getenv("HOME")); 检查命令行参数: while ((c = getopt(argc, argv, "mtThfFdvVqprRgciInx:U:G:C:L:M:")) != -1) { switch (c) { ......... }} 参数v的处理:option_verbose++; 参数里有几个v,option_verbose就等于几,用来控制调试输出级别。 如果用了-c或者-v或者-r并且没有-x cmd参数,则打印欢迎信息: if (ast_opt_console || option_verbose || (ast_opt_remote && !ast_opt_exec)) { ast_register_verbose(console_verboser); WELCOME_MESSAGE; } 如果没有开调试则简单打印Booting... if (ast_opt_console && !option_verbose) ast_verbose("[ Booting...\n"); 显示控制台时,不论是本地还是远程,都不能使用-F参数,否则无效: if (ast_opt_always_fork && (ast_opt_remote || ast_opt_console)) { ast_log(LOG_WARNING, "'alwaysfork' is not compatible with console or\ remote console mode; ignored\n"); ast_clear_flag(&ast_options, AST_OPT_FLAG_ALWAYS_FORK); } -F参数设置了AST_OPT_FLAG_ALWAYS_FORK标志位,这意味着允许fork to detach,也就是先fork然后杀死父进程,然后由子进程成为这个session的leader(具体请参考《UNIX环境高级编程》) 如果远程连接到asterisk的,把名字改成rasterisk,目的是让初始化脚本能够判断主asterisk进程是否已经死了。 if (ast_opt_remote) { strcpy(argv[0], "rasterisk"); for (x = 1; x < argc; x++) { argv[x] = argv[0] + 10; } } 下面开始读取主配置文件:ast_readconfig() ast_readconfig() -> cfg=ast_config_load(ast_config_AST_CONFIG_FILE) -> ast_variable_browse(cfg, "files") -> ast_variable_browse(cfg, "directories") -> ast_variable_browse(cfg, "options") 这里的ast_config_AST_CONFIG_FILE就是/etc/asterisk/asterisk.conf,ast_config_load内部用ast_config_new()和ast_confg_internal_load()这两个函数将这个主配置文件和一个ast_config类型的结构体关联起来,然后用ast_variable_browse()函数在这个结构体中查找相应的类别,也就是配置文件中用[]括起来的名字,如果成功找到,则这个函数会返回这个category中的第一个配置行,也就是ast_category->root,然后在for循环中用ast_category->root->next来遍历这个category,root的类型是ast_variable,如果找到某个ast_variable->name匹配,则将ast_variable->value的值赋给相应的全局变量。这就是asterisk读取配置文件的方法。读取完配置文件之后,要记得将ast_config销毁:ast_config_destroy(cfg); 下面是针对-g选项的,如果设置了-g,则用setrlimit保证对产生的core dump文件的大小没有限制: if (ast_opt_dump_core) { struct rlimit l; memset(&l, 0, sizeof(l)); l.rlim_cur = RLIM_INFINITY; l.rlim_max = RLIM_INFINITY; if (setrlimit(RLIMIT_CORE, &l)) { ast_log(LOG_WARNING, "Unable to disable core size resource\ limit: %s\n", strerror(errno)); } } core dump一般默认是关闭的,如果想调试崩溃信息,可以用ulimit -c命令开启。 然后下面一大段代码都是用来修改用户和组权限,以及修改任务优先级的(如果权限允许)。 初始化模拟终端ast_term_init(),默认是VT100; 注册命令core show config mappings(register_config_cli()); read_config_maps()是做配置文件的映射和绑定的; 然后是设置和检查本地或远程终端的连接,ast_tryconnect()这个函数创建了一个本地的UNIX套接字,并且和astrundir所表示的目录下的asterisk.ctl文件建立连接,astrundir在asterisk.conf里配置,这个socket用ast_consock全局变量表示,注意到asterisk.ctl这个套接字文件只有在asterisk启动时才会创建,所以如果connect成功,说明已经有一个asterisk启动了,所以如果没用使用-r参数,则会提示:Asterisk already running on .... Use 'asterisk -r' to connect. 这部分代码说明asterisk通过AF_LOCAL socket来传递cli命令,直接往这个已经连接上的socket进行write。 接下来是向/var/run/asterisk.pid文件中写入进程号,如果设置了ast_opt_always_fork(-F),则先调用daemon(0,0),fork to detach使进程成为后台进程,然后再重新写入asterisk.pid文件新的进程号: #if HAVE_WORKING_FORK if (ast_opt_always_fork || !ast_opt_no_fork) { daemon(0, 0); ast_mainpid = getpid(); /* Blindly re-write pid file since we are forking */ unlink(ast_config_AST_PID); f = fopen(ast_config_AST_PID, "w"); if (f) { fprintf(f, "%ld\n", (long)ast_mainpid); fclose(f); } else ast_log(LOG_WARNING, "Unable to open pid file '%s': %s\n", ast_config_AST_PID, strerror(errno)); } #endif 下面测试线程安全,避免出现死锁:test_for_thread_safety() 然后创建用于和控制台交互的服务器端socket接口: 用socket创建一个本地流套接口ast_socket,然后用bind和/var/run/asterisk.ctl绑定,然后在这个套接口上listen,然后创建后台线程listener轮询套接口: ast_makesocket() { ...... ast_socket = socket(PF_LOCAL, SOCK_STREAM, 0); sunaddr.sun_family = AF_LOCAL; ast_copy_string(sunaddr.sun_path, ast_config_AST_SOCKET,\ sizeof(sunaddr.sun_path)); res = bind(ast_socket, (struct sockaddr *)&sunaddr, sizeof(sunaddr)); res = listen(ast_socket, 2); ast_pthread_create_background(<hread, NULL, listener, NULL); ...... } 在listner这个后台线程中,不停的轮询ast_socket: fds[0].fd = ast_socket; fds[0].events = POLLIN; s = poll(fds, 1, -1); 如果有新的连接,poll返回,调用accept返回一个新的socket: s = accept(ast_socket, (struct sockaddr *)&sunaddr, &len); 在for中寻找一个可用的console,系统默认console的最大连接数是128,如果没有console可用了则打印no more connections allowed,然后继续轮询。如果有console可用,则创建一个套接口对: socketpair(AF_LOCAL, SOCK_STREAM, 0, consoles[x].p); 然后令p[1]这个套接口为非阻塞,并令consoles[x].fd为接收数据的套接口s: flags = fcntl(consoles[x].p[1], F_GETFL); fcntl(consoles[x].p[1], F_SETFL, flags | O_NONBLOCK); consoles[x].fd = s; 然后创建一个netconsole后台线程: ast_pthread_create_background(&consoles[x].t, &attr, netconsole, &consoles[x]); 在netconsole中首先向这个连接上的终端打印一些信息,然后轮询consoles[x].fd和consoles[x].p[0]: if (fds[0].revents) { res = read(con->fd, tmp, sizeof(tmp)); if (res < 1) { break; } tmp[res] = 0; ast_cli_command(con->fd, tmp); } if (fds[1].revents) { res = read(con->p[0], tmp, sizeof(tmp)); if (res < 1) { ast_log(LOG_ERROR, "read returned %d\n", res); break; } res = write(con->fd, tmp, res); if (res < 1) break; } 如果在con->fd中有数据到来,即用户发出的命令,则讲命令赋给tmp,然后调用ast_cli_command执行这个命令,第一个参数con->fd为命令输出的文件描述符,也就是这个套接口本身; p[]这个套接口对用于向用户终端输出信息,socketpair不同于pipe,它是双向的,这里p[1]仍然用于写,p[0]用于读。如果asterisk向con->p[1]写数据,则poll检测到p[0]有数据可读,然后将数据读出,再写到con->fd向用户控制台输出,然后继续轮询。 下面是加入信号集,设置掩码,以及注册信号的相应handler: sigemptyset(&sigs); sigaddset(&sigs, SIGHUP); sigaddset(&sigs, SIGTERM); sigaddset(&sigs, SIGINT); sigaddset(&sigs, SIGPIPE); sigaddset(&sigs, SIGWINCH); pthread_sigmask(SIG_BLOCK, &sigs, NULL); signal(SIGURG, urg_handler); signal(SIGINT, __quit_handler); signal(SIGTERM, __quit_handler); signal(SIGHUP, hup_handler); signal(SIGCHLD, child_handler); signal(SIGPIPE, SIG_IGN); 接下来设置种子并初始化随机数发生器: srand((unsigned int) getpid() + (unsigned int) time(NULL)); initstate((unsigned int) getpid() * 65536 + (unsigned int) time(NULL),\ randompool, sizeof(randompool)); 然后开始一系列的初始化工作: 初始化日志模块,init_logger(); threadstorage_init()注册threadstorage show allocations和threadstorage show summary这两个命令; load_modules(1)加载配置文件/etc/asterisk/modules.conf中标记为preload的模块,再去掉标记为noload的模块; 后面还会再调用一次load_modules(0),它会先加载modules.conf中标记为load的模块,然后判断文件中autoload是否为yes,如果是,则加载/usr/lib/asterisk/目录中所有的模块,最后再去掉配置文件中标记为noload的模块。 现在具体分析一下load_modules()的内部流程: 加载配置文件/etc/asterisk/modules.conf,返回相应的ast_config结构, cfg = ast_config_load(AST_MODULE_CONFIG); 在modules类别中遍历,如果参数是1,则将preload后面的模块加入load_order结构中,如果是0则将load的模块加入load_order中, for (v = ast_variable_browse(cfg, "modules"); v; v = v->next) { if (!strcasecmp(v->name, preload_only ? "preload" : "load")) add_to_load_order(v->value, &load_order); } 如果参数preload为0,并且文件中autoload为yes,则将/usr/lib/asterisk/modules中所有的.so模块加入到load_order中;然后再遍历一次modules类别,将noload标记的模块从load_order中删除,这时所有需要加载的.so模块都已经在load_order中了。 接下来两次遍历load_order,调用load_resource(order->resource, 1)和load_resource(order->resource, 0)分别加载提供全局符号的模块和其它模块,load_resource函数主要是调用load_dynamic_module()函数加载模块在根据返回的相应状态做出处理。load_dynamic_module()函数内部还是调用底层接口dlopen来加载共享库。 接下来是DNS manager的初始化,dnsmgr_init(),它的主要工作就是创建一个调度器上下文,然后注册两个命令dnsmgr reload和dnsmgr status,然后调用do_reload(1)读取配置文件dnsmgr.conf的enable和refreshinterval选项,来决定是否开启managed DNS look up,并创建后台线程以refreshinterval为周期刷新。 ast_http_init()用来配置http服务器的,它注册一个http show status命令,然后调用__ast_http_load,函数读取http.conf,根据配置文件设置相应变量,默认端口号为8088,设置绑定地址,同时web程序所在的目录是什么等。最后调用http_server_start(),函数创建套接口,并在套接口上listen,然后创建一个后台线程http_root对http消息进行处理。 ast_http_init之后是ast_channels_init(),它仅注册两个命令core show channeltypes和core show channeltype。 init_manager()用来注册管理命令,使用ast_manager_register2函数, int ast_manager_register2( const char *action, int authority, int (*func)(struct mansession *s, const struct message *m), const char *synopsis, const char *description); 第一个参数是命令的名字,第二个是执行这条命令需要的authority,比如是否能读取和设置命令,是否能读取和设置配置文件等等,第三个参数是相关的执行函数,也就是action,第四个参数是在CLI中显示出来的说明,第五个是对命令的解释。然后就是读取manager.conf配置文件,设置相关参数,创建套接口asock,然后创建后台线程accept_thread,在accept_thread线程中建立一个mansession类型的结构体s,然后在刚刚创建的套接口(asock)上轮询,如果有数据进来,就调用accept并返回另一个套接口as,将as赋给mansession的fd参数用来读取命令,然后将这个mansession结构的eventq指向主事件队列的最后一项,做完这些准备工作之后,就创建后台线程session_do,传递的参数就是这个mansession类型的s结构体,在session_do中调用do_message(s)来处理消息。 ast_cdr_engine_init()用来创建一个调度上下文以及注册相应的命令,然后用do_reload来读取配置文件cdr.conf和创建后台线程do_cdr。 ast_device_state_engine_init()用来创建一个后台线程轮巡设备的状态,如果发生变化则及时通告。 ast_rtp_init()首先注册rtp,rtcp,stun相关的CLI命令,然后调用ast_rtp_reload()读取配置文件rtp.conf,设置相关参数。 ast_udptl_init()首先注册udptl相关的CLI命令,然后调用ast_udptl_reload()读取配置文件udptl.conf,设置相关参数。 ast_image_init()和ast_file_init()仅仅是注册两个CLI命令。 load_pbx()首先注册dialplan相关的CLI命令,然后调用ast_register_application来注册所有的app,比如dial,busy,background,meetme等,这个app用pbx_builtin结构来定义, static struct pbx_builtin { char name[AST_MAX_APP]; int (*execute)(struct ast_channel *chan, void *data); char *synopsis; char *description; } 比如answer这个app的execute就是pbx_builtin_answer函数。 init_framer()仅仅注册与codec相关的CLI命令。 astdb_init()注册与database相关的CLI命令,然后再注册两个管理命令DBGet和DBPut。 ast_enum_init()读取配置文件enum.conf,初始化支持ENUM的子系统。 然后再执行一次load_modules(0)加载所有其它需要加载的动态链接库。 如果开了调试信息,或者开了console,则打印Asterisk Ready.,如果设置了ast_opt_no_fork标志位,则调用pthread_self()将当前线程的描述符赋给consolethread。 创建管道:pipe(sig_alert_pipe) 然后注册asterisk相关的命令,比如stop,restart,halt等等。 如果开了console,则创建线程monitor_sig_flags,这个线程使用来轮询上面创建的sig_alert_pipe管道的,这个管道的写端被hup_handler和__quit_handler使用,这两个信号处理程序对应的是SIGHUP和SIGINT/SIGTERM信号,他们会先将sig_flags.quit或sig_flags.need_reload设为1,然后向管道sig_alert_pipe[1]写入0,这时线程能检测到sig_alert_pipe[0]有数据可读,于是检测sig_flags.need_reload和sig_flags.quit,如果为1则将他们设为0,然后调用ast_module_reload(NULL)或quit_handler(0, 0, 1, 0)来处理。 最后在一个for循环中接收和处理控制台命令: for (;;) { buf = (char *)el_gets(el, &num); ...... consolehandler((char *)buf); ...... } 到此为止main函数的整个启动流程分析完毕。 May 16 大家好,原Voldcute在线更新所依赖的服务器将于这个月底过期。
Voldcute已经更新,请大家使用更新后的2.1.0版本;
否则,在5月31号之后,您的检查更新功能将不能继续使用;
新的2.1.0版本修正了外城资源级别不能刷新的问题,增加了自动举办活动增加文明度的功能,对于高卢玩纯农的朋友很有用。
为了提高速度,取消了显示玩家排名的功能。
May 08 大家好: 最近收到邮件,由于我已离校超过半年,我的校内邮箱将转为校友身份,将不能继续使用ftp功能。 所以到5月底,现在大家使用的外挂都将不能更新。 我会尽快换一个ftp,并更新voldcute使其重新支持更新,请大家关注blog的日志发布。 另外,支持美国服务器和香港服务器的voldcute版本正在开发中,大陆服务器下一个版本的voldcute将合并所有村庄窗口到一个窗口中。 最近比较忙导致更新缓慢,请原谅。但是不用担心会"太监"。 谢谢关注,游戏愉快。 April 22 最近很多朋友告诉我s9服务器不能登录。
实在抱歉,由于代码的一个低级错误导致了这个问题。
现在已经更新,请重新下载试用。
另外,对于打开窗口太多的问题,请大家放心,一定会合并在一起的
只是最近太忙了,实在没有时间,一有时间我就更新,欢迎大家试用。
再次抱歉
:) March 20 IDE硬盘的序列号只需要调用一个ioctl就可以,google一下就出来了。但是我发现很多人说SCSI硬盘没有序列号,那么在我机器上scsiinfo -a /dev/sda打印出来的是什么。。。。? Vendor: ATA Product: HTS541080G9SA00 Revision level: MB4I Serial Number ' MPBDPAXNHPUY1M' 于是apt-get source scsitools, 分析一下scsiinfo的源码就知道如何获取SCSI硬盘的序列号了,用了SG_IO这个ioctl, 参数是一个sg_io_hdr_t 结构体。想要修改它只需要欺骗应用程序就行了,写一个module,截获这个ioctl并将自己想要的值返回给应用程序。不过2.6内核sys_call_table好像不导出了,那么在/boot/System.map-`uname -r`这个文件中查找sys_call_table的入口地址直接写到程序里,代码如下,把SCSI硬盘序列号改成全A: #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <scsi/scsi.h> #include <scsi/sg.h> void **sys_call_table; #define __NR_ioctl 54 typedef struct{ int a[2]; }id; asmlinkage long (*old_ioctl)(unsigned int fd, unsigned int cmd, unsigned long arg); asmlinkage long new_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg) { if(cmd == SG_IO) { int i; sg_io_hdr_t sgh = *(sg_io_hdr_t *)arg; unsigned char *cmdp = sgh.cmdp; unsigned char *buffer = sgh.dxferp; if(cmdp[0]==0x12 && cmdp[2]==0x80) { int ret = (*old_ioctl)(fd, cmd, arg); int leng = buffer[3]; for(i = 0; i < leng; i++) buffer[4 + i] = 'A'; buffer[4 + i] = '\0'; } else return (*old_ioctl)(fd, cmd, arg); return 0; } else return (*old_ioctl)(fd, cmd, arg); } int syscall_init(void) { printk("syscall_init!\n"); sys_call_table=(void *)0xc02824c0; old_ioctl=sys_call_table[__NR_ioctl]; sys_call_table[__NR_ioctl]=&new_ioctl; return 0; } void syscall_exit(void) { sys_call_table[__NR_ioctl]=old_ioctl; printk("syscall_exit!\n"); } module_init(syscall_init); module_exit(syscall_exit); MODULE_LICENSE("GPL");
insmod这个模块后再scsiinfo -a /dev/sda看看? Vendor: ATA Product: HTS541080G9SA00 Revision level: MB4I Serial Number 'AAAAAAAAAAAAAAAAA' OK,可以用。
|
|
|
|