从v0.18.0版本开始,增加了-r选项,该选项后面可以跟随需要执行的脚本的相对路径和参数,使用该选项后,zenglServer就会以单进程的方式直接在命令行中执行脚本。
页面导航:
zenglServer源代码的相关地址:https://github.com/zenglong/zenglServer 当前版本对应的tag标签为:v0.18.0
从v0.18.0版本开始,增加了-r选项,该选项后面可以跟随需要执行的脚本的相对路径和参数,使用该选项后,zenglServer就会以单进程的方式直接在命令行中执行脚本。例如:
[root@localhost zenglServerTest]# ./zenglServer -r "/v0_18_0/test_cmd.zl?maxsec=3&name=zl" now in cmd name: zl I'll sleep for 1 seconds I'll sleep for 2 seconds I'll sleep for 3 seconds [root@localhost zenglServerTest]#
从上面的例子中可以看到,通过命令行方式执行脚本时,脚本中通过print指令输出的信息会直接显示到命令行终端。
-r选项还可以配合其他选项一起使用,例如,配合-c选项来指定配置文件等:
[root@localhost zenglServerTest]# ./zenglServer -c config.zl -r "/v0_18_0/test_cmd.zl?maxsec=3&name=zl" now in cmd name: zl I'll sleep for 1 seconds I'll sleep for 2 seconds ^C [root@localhost zenglServerTest]#
-r选型后面的脚本的路径,是相对于config.zl配置文件中指定的web根目录的路径的。例如,假设web根目录的路径是"my_webroot",那么上面例子中脚本的路径就会是:"my_webroot/v0_18_0/test_cmd.zl"。
以命令行方式执行脚本时,进程相关的信息也会写入到日志文件中,可以使用-l选型指定日志文件名(也可以不指定,不指定时,默认就是logfile文件名),例如:
[root@localhost zenglServerTest]# ./zenglServer -c config.zl -l logfile_cmd -r "/v0_18_0/test_cmd.zl?maxsec=3&name=zl" now in cmd name: zl I'll sleep for 1 seconds I'll sleep for 2 seconds I'll sleep for 3 seconds [root@localhost zenglServerTest]# cat logfile_cmd **--------- cmd begin ---------*** create master process for cmd [pid:7591] use config: config.zl *** config is in debug mode *** run config.zl complete, config: port: 8083 process_num: 1 webroot: my_webroot session_dir: my_sessions session_expire: 1440 cleaner_interval: 3600 remote_debug_enable: False remote_debugger_ip: 127.0.0.1 remote_debugger_port: 9999 zengl_cache_enable: False shm_enable: False shm_min_size: 307200 verbose: True request_body_max_size: 204800, request_header_max_size: 5120 request_url_max_size: 1024 URL_PATH_SIZE: 120 FULL_PATH_SIZE: 200 2020/02/06 14:33:24 pid:7591 url: /v0_18_0/test_cmd.zl?maxsec=3&name=zl url_path: /v0_18_0/test_cmd.zl full_path: my_webroot/v0_18_0/test_cmd.zl **--------- cmd end return:0 ---------*** [root@localhost zenglServerTest]#
日志中,cmd begin到cmd end之间的内容就是,命令行模式下执行一次脚本时的相关信息,可以看到执行脚本时的进程ID,执行时间,完整的脚本路径信息等。
和-r选型相关的源码位于main.c文件中:
/** * zenglServer启动时会执行的入口函数 */ int main(int argc, char * argv[]) { .......................................................................... char * run_cmd = NULL; // 需要在命令行中执行的脚本的相对路径(包括需要传递给脚本的参数) zlsrv_main_argv = argv; // 通过getopt的C库函数来获取用户在命令行中输入的参数,并根据这些参数去执行不同的操作 while (-1 != (o = getopt(argc, argv, "vhc:l:r:"))) { switch(o){ .......................................................................... // 当使用-r选项时,可以直接在命令行中运行脚本,-r后面需要跟随脚本的url路径和参数信息,例如: ./zenglServer -r "/v0_1_1/test.zl?a=12&b=456" case 'r': run_cmd = optarg; is_run_in_cmd = ZL_EXP_TRUE; // 将is_run_in_cmd设置为TRUE,表示当前在命令行中运行 if(strlen(run_cmd) == 0) { printf("please set script url for -r option\n"); exit(-1); } break; // 当使用-h参数时,会显示出帮助信息,然后直接返回以退出程序 case 'h': printf("usage: ./zenglServer [options]\n" \ "-v show version\n" \ "-c <config file> set config file\n" \ "-l <logfile> set logfile\n" \ "-r <script_url> set script url(include query params) for cmd\n" \ "-h show this help\n"); return 0; default: exit(-1); break; } } .......................................................................... //通过fork创建master主进程,该进程将在后台以守护进程的形式一直运行,并通过该进程来创建执行具体任务的child子进程 if(run_cmd == NULL) { pid_t master_pid = fork(); if(master_pid < 0) { WRITE_LOG_WITH_PRINTF("failed to create master process [%d] %s \n", errno, strerror(errno)); // 创建master进程失败,直接退出 exit(-1); } else if(master_pid > 0) { // 记录master主进程的进程ID write_to_server_log_pipe(WRITE_TO_LOG, "create master process for daemon [pid:%d] \n", master_pid); // 创建完master进程后,直接返回以退出当前进程 return 0; } } else { // 命令行模式下,只需要一个进程,就不需要再创建子进程了 write_to_server_log_pipe(WRITE_TO_LOG, "**--------- cmd begin ---------***\ncreate master process for cmd [pid:%d] \n", getpid()); } .......................................................................... // 如果设置了pidfile文件,则将主进程的进程ID记录到pidfile所指定的文件中(只有在非命令行模式下,才需要执行这步操作) if(run_cmd == NULL) { if(strlen(config_pidfile) > 0) { write_to_server_log_pipe(WRITE_TO_LOG, "pidfile: %s\n", config_pidfile); char master_pid_str[30]; snprintf(master_pid_str, 30, "%d", getpid()); int pidfile_fd = open(config_pidfile, O_WRONLY|O_TRUNC|O_CREAT, 0644); // TODO if(pidfile_fd < 0) { WRITE_LOG_WITH_PRINTF("open %s for pidfile failed [%d] %s \n", config_pidfile, errno, strerror(errno)); } else { write(pidfile_fd, master_pid_str, strlen(master_pid_str)); close(pidfile_fd); } } else { write_to_server_log_pipe(WRITE_TO_LOG, "no pidfile.\n"); } } // 关闭虚拟机,并释放掉虚拟机所分配过的系统资源 zenglApi_Close(VM); // 如果是命令行模式,则通过main_run_cmd函数在命令行中直接运行脚本 if(run_cmd != NULL) { int cmd_ret = main_run_cmd(run_cmd); write_to_server_log_pipe(WRITE_TO_LOG, "**--------- cmd end return:%d ---------***\n\n", cmd_ret); return cmd_ret; } else { .......................................................................... } .......................................................................... } .......................................................................... /** * 当使用了-r选项来运行脚本时,会在主进程中,以命令行的方式直接执行脚本 * 例如: ./zenglServer -r "/v0_1_1/test.zl?a=12&b=456" * 就是直接在命令行中运行test.zl脚本,并向脚本中传递a参数和b参数 * 命令行方式运行时,也需要提供完整的相对路径,以及类似http的请求参数 */ static int main_run_cmd(char * run_cmd) { .......................................................................... if(S_ISDIR(filestatus.st_mode)) { const char * error_str = "it's a directory, can't be run!"; printf("%s\n", error_str); write_to_server_log_pipe(WRITE_TO_PIPE_, "%s\n", error_str); return -1; } else { write_to_server_log_pipe(WRITE_TO_PIPE_, "full_path: %s\n", full_path); // 如果要访问的文件是以.zl结尾的,就将该文件当做zengl脚本来进行编译执行 if(full_length > 3 && S_ISREG(filestatus.st_mode) && (strncmp(full_path + (full_length - 3), ".zl", 3) == 0)) { .......................................................................... return 0; } else { // 只有以.zl结尾的常规文件,才会被当成zengl脚本来执行,其他文件在命令行下会直接报错,并返回 const char * error_str = "it's not a normal zengl script file, can't be run!"; printf("%s\n", error_str); write_to_server_log_pipe(WRITE_TO_PIPE_, "%s\n", error_str); return -1; } } }
从上面源码中可以看到,当使用-r选型以命令行模式执行脚本时,zenglServer不会像常规模式那样,再去额外的创建主进程和工作进程,始终只会是一个进程。这样就不会因为创建进程,而产生额外的系统开销。
此外,还可以看到,在命令行模式下,zenglServer会通过main_run_cmd函数来执行脚本。该函数执行脚本相关的代码,与常规服务端模式下执行脚本的C代码是差不多的,都是先获取脚本的完整路径信息,再通过zengl语言的api接口来运行脚本即可。
当前版本新增了bltIsRunInCmd模块函数,用于判断当前脚本是否是在命令行中运行。该模块函数相关的C源码位于module_builtin.c文件中:
/** * bltIsRunInCmd模块函数,判断当前脚本是否是在命令行中运行 * 返回整数1则表示在命令行中运行,否则就不是命令行模式 * 例如: * if(bltIsRunInCmd()) bltSetImmediatePrint(TRUE); print 'now in cmd'; else print 'must be run in cmd'; bltExit(); endif */ ZL_EXP_VOID module_builtin_is_run_in_cmd(ZL_EXP_VOID * VM_ARG, ZL_EXP_INT argcount) { ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}}; ZL_EXP_BOOL arg_is_run_in_cmd; main_check_is_run_in_cmd(&arg_is_run_in_cmd); if(arg_is_run_in_cmd) // 返回整数1则表示在命令行中运行,否则就不是命令行模式 zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, 1, 0); else zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, 0, 0); }
可以看到,当该模块函数返回1时就表示当前正在命令行中运行,否则就是常规模式下运行。
当前版本还新增了bltSetImmediatePrint模块函数,用于在命令行模式下开启或关闭立即打印模式。该模块函数相关的C源码也位于module_builtin.c文件中:
/** * bltSetImmediatePrint模块函数,在命令行模式下开启或关闭立即打印模式 * 该模块函数的第一个参数is_immediate_print表示是否开启立即打印模式,如果是不为0的整数就表示开启,否则表示关闭 * 在立即打印模式中,命令行下运行的脚本在使用print指令输出信息时,会立即输出到命令行终端 * 示例代码,参考上面的bltIsRunInCmd模块函数的示例 */ ZL_EXP_VOID module_builtin_set_immediate_print(ZL_EXP_VOID * VM_ARG, ZL_EXP_INT argcount) { ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}}; if(argcount < 1) zenglApi_Exit(VM_ARG,"usage: bltSetImmediatePrint(is_immediate_print): integer"); zenglApi_GetFunArg(VM_ARG,1,&arg); if(arg.type != ZL_EXP_FAT_INT) { zenglApi_Exit(VM_ARG,"the first argument [is_immediate_print] of bltSetImmediatePrint must be integer"); } ZL_EXP_BOOL arg_is_immediate_print = ZL_EXP_FALSE; if(arg.val.integer) { arg_is_immediate_print = ZL_EXP_TRUE; } main_set_is_immediate_print(arg_is_immediate_print); zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, 0, 0); }
默认情况下,在命令行中执行脚本时,print指令输出的信息并不会马上显示出来,而是会等到脚本执行完后,再一次性输出来。
如果想要让print指令的输出信息立即显示出来的话,就可以使用上面这个bltSetImmediatePrint模块函数,当传给该函数的第一个参数是不为0的整数时,就可以开启立即打印模式,否则会关闭立即打印模式。
当前版本还增加了bltSleep模块函数,该模块函数可以让当前线程睡眠一段时间。此模块函数相关的C源码也位于module_builtin.c文件中:
/** * bltSleep模块函数,让当前线程睡眠一段时间 * 该模块函数的第一个参数seconds表示睡眠多少秒 * 例如: * for(i=1; i <= maxsec; i++) print 'I\'ll sleep for ' + i + ' seconds'; bltSleep(i); endfor */ ZL_EXP_VOID module_builtin_sleep(ZL_EXP_VOID * VM_ARG, ZL_EXP_INT argcount) { ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}}; if(argcount < 1) zenglApi_Exit(VM_ARG,"usage: bltSleep(seconds): integer"); zenglApi_GetFunArg(VM_ARG,1,&arg); if(arg.type != ZL_EXP_FAT_INT) { zenglApi_Exit(VM_ARG,"the first argument [seconds] of bltSleep must be integer"); } ZL_EXP_LONG retval = (ZL_EXP_LONG)sleep((unsigned int)arg.val.integer); zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, retval, 0); }
该模块函数的第一个参数必须是整数类型,表示需要睡眠多少秒。
为了测试上面介绍过的三个模块函数,当前版本在my_webroot目录中增加了v0_18_0子目录,并在该子目录中新增了一个名为test_cmd.zl的测试脚本,该脚本的代码如下:
use builtin, request; def TRUE 1; def FALSE 0; if(bltIsRunInCmd()) bltSetImmediatePrint(TRUE); print 'now in cmd'; else print 'must be run in cmd'; bltExit(); endif querys = rqtGetQuery(); if(querys['name']) print 'name: ' + querys['name']; endif maxsec = bltInt(querys['maxsec']); if(maxsec <= 0) bltExit('invalid maxsec arg'); endif for(i=1; i <= maxsec; i++) print 'I\'ll sleep for ' + i + ' seconds'; bltSleep(i); endfor
上面代码中,会先通过bltIsRunInCmd模块函数来判断当前是否处于命令行模式。如果处于命令行模式,就通过bltSetImmediatePrint开启立即打印模式,这样后面的print指令输出的信息就会立即显示到命令行终端了。
可以看到,命令行模式下,脚本依然可以通过rqtGetQuery模块函数来获取传递给脚本的参数。最后在底部的for循环中,还使用了bltSleep模块函数来测试睡眠效果。
绳锯木断,水滴石穿。
—— 罗大经《鹤林玉露》