从v0.18.0版本开始,增加了-r选项,该选项后面可以跟随需要执行的脚本的相对路径和参数,使用该选项后,zenglServer就会以单进程的方式直接在命令行中执行脚本。

    页面导航:

项目下载地址:

    zenglServer源代码的相关地址:https://github.com/zenglong/zenglServer  当前版本对应的tag标签为:v0.18.0

zenglServer v0.18.0:

    从v0.18.0版本开始,增加了-r选项,该选项后面可以跟随需要执行的脚本的相对路径和参数,使用该选项后,zenglServer就会以单进程的方式直接在命令行中执行脚本。例如:

[[email protected] 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
[[email protected] zenglServerTest]#

    从上面的例子中可以看到,通过命令行方式执行脚本时,脚本中通过print指令输出的信息会直接显示到命令行终端。

    -r选项还可以配合其他选项一起使用,例如,配合-c选项来指定配置文件等:

[[email protected] 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
[[email protected] zenglServerTest]# 

    -r选型后面的脚本的路径,是相对于config.zl配置文件中指定的web根目录的路径的。例如,假设web根目录的路径是"my_webroot",那么上面例子中脚本的路径就会是:"my_webroot/v0_18_0/test_cmd.zl"。

    以命令行方式执行脚本时,进程相关的信息也会写入到日志文件中,可以使用-l选型指定日志文件名(也可以不指定,不指定时,默认就是logfile文件名),例如:

[[email protected] 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
[[email protected] 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 ---------***

[[email protected] zenglServerTest]# 

    日志中,cmd begin到cmd end之间的内容就是,命令行模式下执行一次脚本时的相关信息,可以看到执行脚本时的进程ID,执行时间,完整的脚本路径信息等。

-r选型执行脚本的相关源码:

    和-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模块函数:

    当前版本新增了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模块函数:

    当前版本还新增了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模块函数:

    当前版本还增加了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);
}

    该模块函数的第一个参数必须是整数类型,表示需要睡眠多少秒。

test_cmd.zl测试脚本:

    为了测试上面介绍过的三个模块函数,当前版本在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模块函数来测试睡眠效果。

结束语:

    绳锯木断,水滴石穿。

—— 罗大经《鹤林玉露》

 

上下篇

下一篇: zenglServer v0.19.0 增加redis缓存相关的模块

上一篇: zenglServer v0.17.0 设置精简日志模式,设置允许上传文件大小,日志分割等

相关文章

zenglServer v0.16.0 增加curlSetPostByHashArray,curlSetHeaderByArray模块函数,调整进程名称等

zenglServer v0.6.0 session会话

zenglServer v0.15.0 - v0.15.1 增加curl模块,用于执行数据抓取操作

zenglServer v0.8.0-v0.8.1

zenglServer v0.21.0 增加base64编解码相关的内建模块函数

zenglServer v0.17.0 设置精简日志模式,设置允许上传文件大小,日志分割等