在curl模块中增加了两个模块函数:curlSetPostByHashArray和curlSetHeaderByArray模块函数,在curlEasySetopt模块函数中增加了COOKIEFILE,COOKIEJAR,COOKIE,PROXY,POSTFIELDS,VERBOSE以及STDERR选项,调整了进程名称等。
页面导航:
zenglServer源代码的相关地址:https://github.com/zenglong/zenglServer 当前版本对应的tag标签为:v0.16.0
该版本新增的功能如下:
在curl模块中增加了两个模块函数:curlSetPostByHashArray和curlSetHeaderByArray模块函数。
在curlEasySetopt模块函数中增加了COOKIEFILE,COOKIEJAR,COOKIE,PROXY,POSTFIELDS,VERBOSE以及STDERR选项。
在curlEasyPerform模块函数中增加了&ptr参数,可以获取到指向了抓取的数据的指针,利用该指针可以保存图像等二进制数据。
在builtin模块中增加了bltFree及bltReadFile模块函数。
此外,在主进程名称中增加了端口号,当前工作目录等,在启动zenglServer时还可以使用-l命令行参数来设置日志文件名。
当前版本新增的很多测试脚本都需要启动两个zenglServer,一个绑定到8083端口,另一个绑定到8084端口。当通过8083端口访问当前版本的一些测试脚本时,它们会再通过curl去请求8084端口对应的zenglServer里的脚本,以测试POST请求等。
要新启动一个绑定到8084端口的zenglServer,可以拷贝一份config.zl配置文件,并在该配置文件中修改端口号为8084,在启动时通过-c参数指定该配置文件即可:
[parallels@localhost zenglServerTest]$ ./zenglServer [parallels@localhost zenglServerTest]$ cat config_test.zl ..................................................................... port = 8084; // 绑定的端口 ..................................................................... [parallels@localhost zenglServerTest]$ ./zenglServer -c config_test.zl [parallels@localhost zenglServerTest]$ ps aux | grep zenglServer paralle+ 22252 0.0 0.1 139068 2348 ? Ss 14:38 0:00 zenglServer: master[8083] cwd:/media/psf/Home/zenglServerTest -c config.zl -l logfile paralle+ 22253 0.0 0.0 155460 1752 ? Sl 14:38 0:00 zenglServer: child(0) ppid:22252 paralle+ 22256 0.0 0.0 139068 1752 ? S 14:38 0:00 zenglServer: cleaner ppid:22252 paralle+ 22346 0.0 0.1 139068 2348 ? Ss 14:38 0:00 zenglServer: master[8084] cwd:/media/psf/Home/zenglServerTest -c config_test.zl -l logfile paralle+ 22347 0.0 0.0 155460 1756 ? Sl 14:38 0:00 zenglServer: child(0) ppid:22346 paralle+ 22350 0.0 0.0 139068 1752 ? S 14:38 0:00 zenglServer: cleaner ppid:22346 paralle+ 22368 0.0 0.0 112720 988 pts/1 S+ 14:38 0:00 grep --color=auto zenglServer [parallels@localhost zenglServerTest]$
上面启动了两个zenglServer,一个绑定的端口是8083(通过默认的config.zl配置文件来启动),另一个绑定的端口是8084(通过拷贝和修改过的config_test.zl配置文件来启动),在ps显示的进程列表里也可以看到它们绑定的端口号。
curlSetPostByHashArray,curlSetHeaderByArray模块函数:
curlSetPostByHashArray和curlSetHeaderByArray模块函数的代码,位于module_curl.c文件中:
/** * curlSetPostByHashArray模块函数,通过哈希数组来设置multipart/form-data类型的POST请求, * 该模块函数的第一个参数必须是有效的my_curl_handle_struct类型的指针,该指针由curlEasyInit模块函数返回, * 第二个参数hash_array是用于设置POST请求的哈希数组,数组中的每个带有字符串key的成员都对应一个POST请求的名值对信息, * 数组成员的字符串key将作为POST请求的name,数组成员的值将作为POST请求的值, * 当需要在POST请求中发送文件时,可以使用@file_path的格式来设置需要发送的文件的路径(该路径是相对于当前主执行脚本的路径), * 例如:@upload/upload_image.jpg 表示将 upload/upload_image.jpg 对应的文件内容通过POST请求发送出去, * 还可以设置发送文件的Content-Type类型,只需在路径后面跟随Content-Type类型名即可, * 文件路径和Content-Type类型名之间通过英文半角逗号隔开, * 例如:@upload/kernel_shell.png,image/png 表示需要发送的文件路径为upload/kernel_shell.png,Content-Type类型为image/png, * * 示例代码如下: use builtin, curl, request; def TRUE 1; def FALSE 0; rqtSetResponseHeader("Content-Type: text/html; charset=utf-8"); print 'curl version: ' + curlVersion() + '<br/>'; curl_handle = curlEasyInit(); curlEasySetopt(curl_handle, 'URL', 'http://127.0.0.1:8084/v0_2_0/post.zl'); curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0'); curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE); curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE); curlEasySetopt(curl_handle, 'TIMEOUT', 30); data['name'] = 'zenglong'; data['job'] = 'programmer'; data['age'] = 30; data['money'] = 550.35; data['myjpg'] = '@upload/upload_image.jpg'; data['mypng'] = '@upload/kernel_shell.png,image/png'; curlSetPostByHashArray(curl_handle, data); ret = curlEasyPerform(curl_handle, &content); if(ret == 0) print content; else print 'error: ' + curlEasyStrError(ret); endif curlEasyCleanup(curl_handle); 上面脚本中,先通过data数组设置需要发送的POST请求所对应的名值对信息, 例如上面的 data['job'] = 'programmer'; 表示将要设置一个名为job,值为programmer的POST请求, 在创建好包含名值对的哈希数组后,接着就可以通过curlSetPostByHashArray模块函数将数组中的数据和底层的curl操作指针进行绑定, 当使用curlEasyPerform执行具体的请求操作时,就会根据这些数据来创建一个multipart/form-data类型的POST请求。 上面脚本在执行时,会构建出类似如下所示的multipart/form-data类型的POST请求: POST /v0_2_0/post.zl HTTP/1.1 ................................ Content-Type: multipart/form-data; boundary=------------------------------cb39d046a2e0 ................................ ------------------------------cb39d046a2e0 Content-Disposition: form-data; name="name" zenglong ------------------------------cb39d046a2e0 Content-Disposition: form-data; name="job" programmer ------------------------------cb39d046a2e0 Content-Disposition: form-data; name="age" 30 ------------------------------cb39d046a2e0 Content-Disposition: form-data; name="money" 550.35 ------------------------------cb39d046a2e0 Content-Disposition: form-data; name="myjpg"; filename="upload_image.jpg" Content-Type: image/jpeg .......JFIF.............................. ......................................... ------------------------------cb39d046a2e0 Content-Disposition: form-data; name="mypng"; filename="kernel_shell.png" Content-Type: image/png ..PNG.................................... ......................................... ------------------------------cb39d046a2e0-- 该模块函数在底层会通过curl_formadd库函数,来进行实际的构建multipart/form-data类型的POST请求的操作, 该库函数的官方地址:https://curl.haxx.se/libcurl/c/curl_formadd.html */ ZL_EXP_VOID module_curl_set_post_by_hash_array(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount) { ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}}; if(argcount < 2) zenglApi_Exit(VM_ARG,"usage: curlSetPostByHashArray(curl_handle, hash_array): integer"); zenglApi_GetFunArg(VM_ARG,1,&arg); if(arg.type != ZL_EXP_FAT_INT) { zenglApi_Exit(VM_ARG,"the first argument [curl_handle] of curlSetPostByHashArray must be integer"); } ....................................................................... } /** * curlSetHeaderByArray模块函数,设置自定义的HTTP请求头, * 该模块函数的第一个参数必须是有效的my_curl_handle_struct类型的指针,该指针由curlEasyInit模块函数返回, * 第二个参数array必须是一个数组,数组中的每一项都对应一个自定义的请求头, * 如果自定义的请求头中只包含请求头名,而不包含对应的值时,表示如果存在该名称对应的请求头的话,就将其移除掉, * 例如:'Accept:' 就表示移除掉Accept请求头, * 当自定义的请求头名和已存在的请求头名称相同时,表示修改该请求头对应的值, * 例如:'Host: example.com',表示将已存在的Host请求头的值修改为 example.com, * 可以设置一个空的没有值的请求头,例如: 'X-silly-header;' 表示设置一个名为X-silly-header,值为空的请求头, * 不过自定义这种没有值的请求头,在低版本的curl库中并不支持,例如:7.15和7.19的版本。 * * 示例代码如下: use builtin, curl, request; def TRUE 1; def FALSE 0; rqtSetResponseHeader("Content-Type: text/html; charset=utf-8"); print 'curl version: ' + curlVersion() + '<br/>'; curl_handle = curlEasyInit(); curlEasySetopt(curl_handle, 'URL', 'http://127.0.0.1:8084/v0_5_0/show_header.zl'); curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0'); curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE); curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE); curlEasySetopt(curl_handle, 'TIMEOUT', 30); curlSetHeaderByArray(curl_handle, bltArray('Accept:', 'Another: yes', 'Host: example.com', 'X-silly-header;')); ret = curlEasyPerform(curl_handle, &content); if(ret == 0) print content; else print 'error: ' + curlEasyStrError(ret); endif curlEasyCleanup(curl_handle); 上面脚本中,通过curlSetHeaderByArray添加了一个Another: yes的请求头,移除了Accept请求头,修改了Host请求头, 还设置了一个名为X-silly-header的值为空的请求头,该脚本的执行结果类似如下所示: curl version: libcurl/7.29.0 NSS/3.34 zlib/1.2.11 libidn/1.28 libssh2/1.4.3 请求头信息: User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0 Another: yes Host: example.com X-silly-header: 该模块函数在底层会通过curl_slist_append库函数,来执行具体的构建自定义请求头的操作, 该库函数的官方地址:https://curl.haxx.se/libcurl/c/curl_slist_append.html */ ZL_EXP_VOID module_curl_set_header_by_array(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount) { ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}}; if(argcount < 2) zenglApi_Exit(VM_ARG,"usage: curlSetHeaderByArray(curl_handle, array): integer"); zenglApi_GetFunArg(VM_ARG,1,&arg); if(arg.type != ZL_EXP_FAT_INT) { zenglApi_Exit(VM_ARG,"the first argument [curl_handle] of curlSetHeaderByArray must be integer"); } ....................................................................... }
在my_webroot/v0_16_0目录中增加了test_post.zl和test_set_header.zl测试脚本,用于测试上面这两个模块函数,其中,test_post.zl脚本的代码如下:
use builtin, curl, request; def TRUE 1; def FALSE 0; rqtSetResponseHeader("Content-Type: text/html; charset=utf-8"); print 'curl version: ' + curlVersion() + '<br/>'; curl_handle = curlEasyInit(); curlEasySetopt(curl_handle, 'URL', 'http://127.0.0.1:8084/v0_2_0/post.zl'); curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0'); curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE); curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE); curlEasySetopt(curl_handle, 'TIMEOUT', 30); data['name'] = 'zenglong'; data['job'] = 'programmer'; data['age'] = 30; data['money'] = 550.35; data['myjpg'] = '@upload/upload_image.jpg'; data['mypng'] = '@upload/kernel_shell.png,image/png'; curlSetPostByHashArray(curl_handle, data); ret = curlEasyPerform(curl_handle, &content); if(ret == 0) print content; else print 'error: ' + curlEasyStrError(ret); endif curlEasyCleanup(curl_handle);
上面脚本中,先通过data数组设置了需要发送的POST请求所对应的名值对信息,例如上面的 data['job'] = 'programmer'; 表示将要设置一个名为job,值为programmer的POST请求。
在创建好包含名值对的哈希数组后,接着就可以通过curlSetPostByHashArray模块函数将数组中的数据和curl_handle进行绑定。当使用curlEasyPerform执行具体的请求操作时,就会根据这些数据来创建一个multipart/form-data类型的POST请求。
上面脚本还使用@file_path的格式,设置了需要发送的文件的路径,例如:@upload/upload_image.jpg 表示将 upload/upload_image.jpg 对应的文件内容通过POST请求发送出去。
@upload/kernel_shell.png,image/png 表示需要发送的文件路径为upload/kernel_shell.png,Content-Type类型为image/png,文件路径和Content-Type类型之间通过英文半角逗号隔开。
上面这个脚本会通过curl向8084端口中的v0_2_0/post.zl脚本发送POST请求,并将POST请求返回的结果显示出来,test_post.zl脚本的执行结果如下:
test_post.zl在浏览器中的执行结果
test_set_header.zl脚本(用于测试curlSetHeaderByArray模块函数)的代码如下:
use builtin, curl, request; def TRUE 1; def FALSE 0; rqtSetResponseHeader("Content-Type: text/html; charset=utf-8"); print 'curl version: ' + curlVersion() + '<br/>'; curl_handle = curlEasyInit(); curlEasySetopt(curl_handle, 'URL', 'http://127.0.0.1:8084/v0_5_0/show_header.zl'); curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0'); curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE); curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE); curlEasySetopt(curl_handle, 'TIMEOUT', 30); curlSetHeaderByArray(curl_handle, bltArray('Accept:', 'Another: yes', 'Host: example.com', 'X-silly-header;')); ret = curlEasyPerform(curl_handle, &content); if(ret == 0) print content; else print 'error: ' + curlEasyStrError(ret); endif curlEasyCleanup(curl_handle);
上面脚本中,通过curlSetHeaderByArray模块函数添加了一个Another: yes的请求头,移除了Accept请求头,修改了Host请求头,还设置了一个名为X-silly-header的值为空的请求头。
该脚本的执行结果如下:
test_set_header.zl脚本在浏览器中的执行结果
当前版本还在curlEasySetopt模块函数中,增加了COOKIEFILE,COOKIEJAR,COOKIE,PROXY,POSTFIELDS,VERBOSE以及STDERR选项。相关的C代码如下:
/** * curlEasySetopt模块函数,设置curl抓取相关的选项,例如:抓取的目标地址,需要使用的用户代理等 * 该模块函数的第一个参数必须是有效的my_curl_handle_struct指针,该指针由curlEasyInit模块函数返回, * 第二个参数是字符串类型的选项名称,暂时只支持以下几个选项: * 'URL':表示需要抓取的目标地址 * 'USERAGENT':需要设置的用户代理 * 'FOLLOWLOCATION':当抓取到重定向页面时,是否进行重定向操作 * 'SSL_VERIFYPEER':是否校验SSL证书 * 'TIMEOUT': 设置超时时间 * 'COOKIEFILE': 设置需要读cookie的文件名,当需要发送cookie信息时,curl会读取该文件,并将其中的cookie作为请求发送出去 * 'COOKIEJAR': 设置需要写入cookie的文件名,当curl获取到的响应头中包含了设置cookie的信息时,会将这些cookie写入到指定的文件 * 'COOKIE': 设置自定义的cookie * 'PROXY': 设置http,socks5之类的代理,socks协议的代理需要7.21.7及以上的版本才支持,低版本的curl库只支持http协议的代理 * 'POSTFIELDS': 设置application/x-www-form-urlencoded类型的POST请求 * 'VERBOSE': 设置是否输出详细的连接信息,包含了请求头和响应头在内,方便调试,这些连接信息会输出到 STDERR 所指定的文件中 * 'STDERR': 当开启了VERBOSE时,详细的连接信息会保存到STDERR所指定的文件中 * * 第三个参数是需要设置的具体的选项值, * * 当第二个参数是'URL','USERAGENT','COOKIE','PROXY','POSTFIELDS'时, * 选项值必须是字符串类型,表示需要设置的url地址,用户代理等, * 当第二个参数是'COOKIEFILE','COOKIEJAR'时,选项值也是字符串,表示需要读取和写入cookie的文件的路径(该路径是相对于当前主执行脚本的) * * 当第二个参数是'STDERR'时,可以有两个选项值: * 第一个选项值option_value表示相对于当前主执行脚本的文件路径,即需要输出VERBOSE信息到哪个文件, * 第二个选项值option_value2是可选的,表示需要以什么模式来打开该文件,默认是wb表示以写入模式打开文件,该模式会清空文件中原有的内容, * 如果option_value2设置为ab,则表示以追加的方式打开文件,VERBOSE信息会追加到文件的末尾。 * * 当第二个参数是'VERBOSE'时,选项值必须是整数类型,表示是否输出详细的连接信息,默认是0,即不输出连接信息,如果要输出连接信息,可以将选项值设置为1 * 当第二个参数是'FOLLOWLOCATION'时,选项值必须是整数类型,表示是否进行重定向操作,默认是0,即不进行重定向,需要进行重定向的,可以将选项值设置为1 * 当第二个参数是'SSL_VERIFYPEER'时,选项值必须是整数类型,表示是否校验SSL证书,默认是1,即需要进行校验,如果不需要校验,可以将选项值设置为0 * 当第二个参数是'TIMEOUT'时,选项值必须是整数类型,表示需要设置的超时时间 * * 和'URL','USERAGENT','FOLLOWLOCATION','SSL_VERIFYPEER','TIMEOUT'选项相关的例子,请参考curlEasyPerform模块函数的注释部分 * 和'COOKIEFILE'选项相关的例子可以参考 my_webroot/v0_16_0/test_cookiefile.zl 脚本对应的代码 * 和'COOKIEJAR'选项相关的例子可以参考 my_webroot/v0_16_0/test_cookiejar.zl 脚本对应的代码 * 和'COOKIE'选项相关的例子可以参考 my_webroot/v0_16_0/test_cookie.zl 脚本对应的代码 * 和'PROXY'选项相关的例子可以参考 my_webroot/v0_16_0/test_proxy.zl 脚本对应的代码 * 和'POSTFIELDS'选项相关的例子可以参考 my_webroot/v0_16_0/test_postfields.zl 脚本对应的代码 * 和'VERBOSE','STDERR'选项相关的例子可以参考 my_webroot/v0_16_0/test_verbose.zl 脚本对应的代码 * * 该模块函数最终会通过curl_easy_setopt库函数去执行具体的操作, * 该库函数的官方地址为:https://curl.haxx.se/libcurl/c/curl_easy_setopt.html */ ZL_EXP_VOID module_curl_easy_setopt(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount) { ...................................................................................... char * options_str[] = { "URL", "USERAGENT", "FOLLOWLOCATION", "SSL_VERIFYPEER", "TIMEOUT", "COOKIEFILE", "COOKIEJAR", "COOKIE", "PROXY", "POSTFIELDS", "VERBOSE", "STDERR" }; int options_str_len = sizeof(options_str)/sizeof(options_str[0]); CURLoption options_enum[] = { CURLOPT_URL, CURLOPT_USERAGENT, CURLOPT_FOLLOWLOCATION, CURLOPT_SSL_VERIFYPEER, CURLOPT_TIMEOUT, CURLOPT_COOKIEFILE, CURLOPT_COOKIEJAR, CURLOPT_COOKIE, CURLOPT_PROXY, CURLOPT_POSTFIELDS, CURLOPT_VERBOSE, CURLOPT_STDERR }; ...................................................................................... zenglApi_GetFunArg(VM_ARG,3,&arg); CURLcode retval; switch(option) { case CURLOPT_URL: case CURLOPT_USERAGENT: case CURLOPT_COOKIEFILE: case CURLOPT_COOKIEJAR: case CURLOPT_COOKIE: case CURLOPT_PROXY: case CURLOPT_POSTFIELDS: if(arg.type == ZL_EXP_FAT_STR) { char * option_value = st_curl_process_str(VM_ARG, my_data, option, my_curl_handle, arg.val.str); retval = curl_easy_setopt(curl_handle, option, option_value); } else { zenglApi_Exit(VM_ARG,"the third argument [option_value] of curlEasySetopt must be string when [option_name] is %s", options_str[opt_idx]); } break; case CURLOPT_STDERR: if(arg.type == ZL_EXP_FAT_STR) { if(my_curl_handle->stderr_stream != NULL) { fclose(my_curl_handle->stderr_stream); } char * filename = arg.val.str; char full_path[FULL_PATH_SIZE]; builtin_make_fullpath(full_path, filename, my_data); char * mode = "wb"; if(argcount > 3) { zenglApi_GetFunArg(VM_ARG,4,&arg); if(arg.type != ZL_EXP_FAT_STR) { zenglApi_Exit(VM_ARG,"the fourth argument [option_value2] of curlEasySetopt must be string when [option_name] is %s", options_str[opt_idx]); } mode = arg.val.str; } my_curl_handle->stderr_stream = fopen(full_path, mode); retval = curl_easy_setopt(curl_handle, option, my_curl_handle->stderr_stream); } else { zenglApi_Exit(VM_ARG,"the third argument [option_value] of curlEasySetopt must be string when [option_name] is %s", options_str[opt_idx]); } break; case CURLOPT_FOLLOWLOCATION: case CURLOPT_SSL_VERIFYPEER: case CURLOPT_TIMEOUT: case CURLOPT_VERBOSE: if(arg.type == ZL_EXP_FAT_INT) { long option_value = arg.val.integer; retval = curl_easy_setopt(curl_handle, option, option_value); } else { zenglApi_Exit(VM_ARG,"the third argument [option_value] of curlEasySetopt must be integer when [option_name] is %s", options_str[opt_idx]); } break; default: zenglApi_Exit(VM_ARG, "the second argument [option_name] of curlEasySetopt is invalid: %s", options_str[opt_idx]); break; } if(retval == CURLE_OK) zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, 0, 0); else if(retval > 0) zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, (ZL_EXP_LONG)retval, 0); else zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, -1, 0); }
从上面的代码注释中可以看到,新增的几个选项的作用如下:
'COOKIEFILE':设置需要读cookie的文件名,当需要发送cookie信息时,curl会读取该文件,并将其中的cookie作为请求发送出去。
'COOKIEJAR':设置需要写入cookie的文件名,当curl获取到的响应头中包含了设置cookie的信息时,会将这些cookie写入到指定的文件。
'COOKIE':设置自定义的cookie。
'PROXY':设置http,socks5之类的代理,socks协议的代理需要7.21.7及以上的版本才支持,低版本的curl库只支持http协议的代理。
'POSTFIELDS':设置application/x-www-form-urlencoded类型的POST请求。
'VERBOSE':设置是否输出详细的连接信息,包含了请求头和响应头在内,方便调试,这些连接信息会输出到 STDERR 所指定的文件中。
'STDERR':当开启了VERBOSE时,详细的连接信息会保存到STDERR所指定的文件中。
和'COOKIEFILE'选项相关的示例脚本为:my_webroot/v0_16_0/test_cookiefile.zl脚本:
use builtin, curl, request; def TRUE 1; def FALSE 0; rqtSetResponseHeader("Content-Type: text/html; charset=utf-8"); print 'curl version: ' + curlVersion() + '<br/>'; curl_handle = curlEasyInit(); curlEasySetopt(curl_handle, 'URL', 'http://127.0.0.1:8084/v0_5_0/show_header.zl'); curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0'); curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE); curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE); curlEasySetopt(curl_handle, 'TIMEOUT', 30); curlEasySetopt(curl_handle, 'COOKIEFILE', 'cookies.txt'); ret = curlEasyPerform(curl_handle, &content); if(ret == 0) print content; else print 'error: ' + curlEasyStrError(ret); endif curlEasyCleanup(curl_handle);
'COOKIEFILE'选项对应的选项值表示需要读取cookie的文件的路径(该路径是相对于当前主执行脚本的)。
在本脚本中,'COOKIEFILE'的选项值就是cookies.txt,如果该文件存在,curl在发送网络请求时,就会去读取该文件,并将文件中的cookie作为请求头发送出去。如果cookies.txt文件不存在,则请求头中就不会包含cookie信息。
我们可以先执行my_webroot/v0_16_0/test_cookiejar.zl脚本,以生成cookies.txt文件,然后再执行本脚本,就可以在发送的请求头中看到cookies.txt文件中的Cookie信息了:
执行test_cookiejar.zl脚本生成cookies.txt文件
test_cookiefile.zl脚本在浏览器中的执行结果
和'COOKIEJAR'选项相关的示例脚本为:my_webroot/v0_16_0/test_cookiejar.zl,该脚本的代码如下:
use builtin, curl, request; def TRUE 1; def FALSE 0; rqtSetResponseHeader("Content-Type: text/html; charset=utf-8"); print 'curl version: ' + curlVersion() + '<br/>'; curl_handle = curlEasyInit(); curlEasySetopt(curl_handle, 'URL', 'http://127.0.0.1:8084/v0_5_0/set_header.zl'); curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0'); curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE); curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE); curlEasySetopt(curl_handle, 'TIMEOUT', 30); curlEasySetopt(curl_handle, 'COOKIEJAR', 'cookies.txt'); ret = curlEasyPerform(curl_handle, &content); curlEasyCleanup(curl_handle); if(ret == 0) ret = bltReadFile('cookies.txt', &file_content, &file_size); if(ret == 0) print 'cookies.txt file size: ' + file_size; print 'file content: \n' + file_content; else print 'read cookies.txt failed, maybe the file does not exists, or open failed.'; endif else print 'error: ' + curlEasyStrError(ret); endif
'COOKIEJAR'选项对应的选项值表示需要写入cookie的文件的路径(该路径是相对于当前主执行脚本的),当curl获取到的响应头中包含了设置cookie的信息时,就会将这些cookie写入到指定的文件中,在本脚本中,需要写入cookie信息的文件就是'cookies.txt'。
test_cookiejar.zl脚本的执行结果如下:
[parallels@localhost zenglServerTest]$ curl http://127.0.0.1:8083/v0_16_0/test_cookiejar.zl curl version: libcurl/7.29.0 NSS/3.34 zlib/1.2.11 libidn/1.28 libssh2/1.4.3<br/> cookies.txt file size: 228 file content: # Netscape HTTP Cookie File # http://curl.haxx.se/docs/http-cookies.html # This file was generated by libcurl! Edit at your own risk. 127.0.0.1 FALSE /v0_5_0/ FALSE 0 name zengl 127.0.0.1 FALSE /v0_5_0/ FALSE 0 hobby play game [parallels@localhost zenglServerTest]$ cat my_webroot/v0_16_0/cookies.txt # Netscape HTTP Cookie File # http://curl.haxx.se/docs/http-cookies.html # This file was generated by libcurl! Edit at your own risk. 127.0.0.1 FALSE /v0_5_0/ FALSE 0 name zengl 127.0.0.1 FALSE /v0_5_0/ FALSE 0 hobby play game [parallels@localhost zenglServerTest]$
test_cookiejar.zl脚本会通过curl去请求 http://127.0.0.1:8084/v0_5_0/set_header.zl 脚本,set_header.zl脚本在执行时会返回 Set-Cookie: name=zengl 之类的设置Cookie的响应头,curl在获取到这些响应头信息后,就会将需要设置的Cookie写入到'COOKIEJAR'所指定的cookies.txt文件中。
和'COOKIE'选项相关的示例脚本为:my_webroot/v0_16_0/test_cookie.zl脚本:
use builtin, curl, request; def TRUE 1; def FALSE 0; rqtSetResponseHeader("Content-Type: text/html; charset=utf-8"); print 'curl version: ' + curlVersion() + '<br/>'; curl_handle = curlEasyInit(); curlEasySetopt(curl_handle, 'URL', 'http://127.0.0.1:8084/v0_5_0/show_header.zl'); curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0'); curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE); curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE); curlEasySetopt(curl_handle, 'TIMEOUT', 30); curlEasySetopt(curl_handle, 'COOKIE', 'name=daniel; present=yes; job=programmer; =hello world'); ret = curlEasyPerform(curl_handle, &content); if(ret == 0) print content; else print 'error: ' + curlEasyStrError(ret); endif curlEasyCleanup(curl_handle);
'COOKIE'选项值必须是字符串,表示自定义的Cookie信息,curl在发送请求时,会将这些自定义的Cookie作为请求头发送出去。test_cookie.zl脚本的执行结果如下:
test_cookie.zl脚本在浏览器中的执行结果
test_cookie.zl脚本会通过curl去请求8084端口的v0_5_0目录中的show_header.zl脚本,该脚本会将请求过来的请求头显示出来,同时还会将请求头中的Cookie转成数组。
和'PROXY'选项相关的示例脚本为:my_webroot/v0_16_0/test_proxy.zl脚本:
use builtin, curl, request; def TRUE 1; def FALSE 0; rqtSetResponseHeader("Content-Type: text/html; charset=utf-8"); print 'curl version: ' + curlVersion(); curl_handle = curlEasyInit(); curlEasySetopt(curl_handle, 'URL', 'https://www.google.com'); curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0'); curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE); curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE); curlEasySetopt(curl_handle, 'TIMEOUT', 30); curlEasySetopt(curl_handle, 'PROXY', 'http://127.0.0.1:8232'); // curlEasySetopt(curl_handle, 'PROXY', 'socks5h://127.0.0.1:1080'); curlEasySetopt(curl_handle, 'VERBOSE', TRUE); curlEasySetopt(curl_handle, 'STDERR', 'dump_proxy.txt'); ret = curlEasyPerform(curl_handle, &content, &size); if(ret == 0) print 'size: ' + size; print 'content: ' + content; else print 'error: ' + curlEasyStrError(ret); endif curlEasyCleanup(curl_handle);
'PROXY'的选项值必须是字符串,表示需要设置的http,socks5之类的代理。其中,socks协议的代理需要7.21.7及以上的版本才支持,低版本的curl库只支持http协议的代理。如果系统中只有低版本的curl库,可以通过polipo将socks代理转为http代理。
上面脚本会通过http代理去访问www.google.com,执行结果如下:
test_proxy.zl脚本在浏览器中的执行结果
由于只通过代理抓取了主体数据,而主体数据中没有包含logo之类的,所以上图中的LOGO就无法显示出来。
在test_proxy.zl脚本中还通过VERBOSE选项,将连接代理的详细信息,输出到了STDERR选项所指定的dump_proxy.txt文件中。如果代理连接失败,可以通过该文件了解到更多信息,方便排查问题:
[parallels@localhost zenglServerTest]$ cat my_webroot/v0_16_0/dump_proxy.txt * About to connect() to proxy 127.0.0.1 port 8232 (#8) * Trying 127.0.0.1... * Connected to 127.0.0.1 (127.0.0.1) port 8232 (#8) * Establish HTTP proxy tunnel to www.google.com:443 > CONNECT www.google.com:443 HTTP/1.1 Host: www.google.com:443 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0 Proxy-Connection: Keep-Alive < HTTP/1.1 200 Tunnel established < * Proxy replied OK to CONNECT request * Initializing NSS with certpath: sql:/etc/pki/nssdb * warning: ignoring value of ssl.verifyhost * skipping SSL peer certificate verification * SSL connection using TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 * Server certificate: * subject: CN=www.google.com,O=Google LLC,L=Mountain View,ST=California,C=US * start date: Dec 04 09:33:00 2018 GMT * expire date: Feb 26 09:33:00 2019 GMT * common name: www.google.com * issuer: CN=Google Internet Authority G3,O=Google Trust Services,C=US > GET / HTTP/1.1 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0 Host: www.google.com Accept: */* < HTTP/1.1 200 OK < Date: Thu, 20 Dec 2018 11:20:44 GMT < Expires: -1 < Cache-Control: private, max-age=0 < Content-Type: text/html; charset=UTF-8 < Strict-Transport-Security: max-age=31536000 < P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info." < Server: gws < X-XSS-Protection: 1; mode=block < X-Frame-Options: SAMEORIGIN < Set-Cookie: 1P_JAR=2018-12-20-11; expires=Sat, 19-Jan-2019 11:20:45 GMT; path=/; domain=.google.com < Set-Cookie: NID=152=vza0OKlZhU6Ewhoom6mYcOJDCTppJNg_AY42XCTHWmjp8k-cxKbevj-GVXD1LHZ-ODKeUlI7rzYweOAaSbu3zFbvy6IdivGlp4TPyO-gg8eExkyPswwW1DFw-28uYvwNSgqCEfkAM2ucza2bAMQ_r82KuvoakIOLXkPQERlj3C8; expires=Fri, 21-Jun-2019 11:20:45 GMT; path=/; domain=.google.com; HttpOnly < Alt-Svc: quic=":443"; ma=2592000; v="44,43,39,35" < Accept-Ranges: none < Vary: Accept-Encoding < Transfer-Encoding: chunked < * Connection #8 to host 127.0.0.1 left intact [parallels@localhost zenglServerTest]$
和'POSTFIELDS'选项相关的示例脚本为:my_webroot/v0_16_0/test_postfields.zl脚本:
use builtin, curl, request; def TRUE 1; def FALSE 0; rqtSetResponseHeader("Content-Type: text/html; charset=utf-8"); print 'curl version: ' + curlVersion() + '<br/>'; curl_handle = curlEasyInit(); curlEasySetopt(curl_handle, 'URL', 'http://127.0.0.1:8084/v0_2_0/post.zl'); curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0'); curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE); curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE); curlEasySetopt(curl_handle, 'TIMEOUT', 30); curlEasySetopt(curl_handle, 'POSTFIELDS', 'name=zengl&hobby=play game!&其他=测试数据,我是谁!'); ret = curlEasyPerform(curl_handle, &content); if(ret == 0) print content; else print 'error: ' + curlEasyStrError(ret); endif curlEasyCleanup(curl_handle);
'POSTFIELDS'的选项值必须是字符串,表示需要设置的application/x-www-form-urlencoded类型的POST请求,上面脚本的执行结果如下:
test_postfields.zl脚本在浏览器中的执行结果
和'VERBOSE'以及'STDERR'选项相关的示例脚本为:my_webroot/v0_16_0/test_verbose.zl脚本:
use builtin, curl, request; def TRUE 1; def FALSE 0; rqtSetResponseHeader("Content-Type: text/html; charset=utf-8"); print 'curl version: ' + curlVersion(); curl_handle = curlEasyInit(); curlEasySetopt(curl_handle, 'URL', 'https://www.example.com'); curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0'); curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE); curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE); curlEasySetopt(curl_handle, 'TIMEOUT', 30); curlEasySetopt(curl_handle, 'VERBOSE', TRUE); curlEasySetopt(curl_handle, 'STDERR', 'dump.txt'); // curlEasySetopt(curl_handle, 'STDERR', 'dump.txt', 'ab'); ret = curlEasyPerform(curl_handle, &content, &size); if(ret == 0) print 'size: ' + size; print 'content: ' + content; else print 'error: ' + curlEasyStrError(ret); endif curlEasyCleanup(curl_handle);
'VERBOSE'的选项值必须是整数类型,表示是否输出详细的连接信息,默认是0,即不输出连接信息。如果要输出连接信息,可以将选项值设置为1。上面脚本中就通过将'VERBOSE'选项设置为TRUE即整数1,从而让curl能够输出详细的连接信息。
'STDERR'可以有两个选项值:
第一个选项值表示相对于当前主执行脚本的文件路径,即需要输出VERBOSE信息到哪个文件中。上面脚本中指定的文件为dump.txt
第二个选项值是可选的,表示需要以什么模式来打开该文件,默认是wb,表示以写入模式来打开文件,该模式会清空文件中原有的内容。如果将其设置为ab,则表示以追加的方式打开文件,这样VERBOSE信息就会追加到文件的末尾。
test_verbose.zl脚本的执行结果如下:
[parallels@localhost zenglServerTest]$ curl http://127.0.0.1:8083/v0_16_0/test_verbose.zl curl version: libcurl/7.29.0 NSS/3.34 zlib/1.2.11 libidn/1.28 libssh2/1.4.3 size: 1270 content: <!doctype html> <html> <head> <title>Example Domain</title> ..................................................................... </body> </html> [parallels@localhost zenglServerTest]$ cat my_webroot/v0_16_0/dump.txt * About to connect() to www.example.com port 443 (#10) * Trying 93.184.216.34... * Connected to www.example.com (93.184.216.34) port 443 (#10) * Initializing NSS with certpath: sql:/etc/pki/nssdb * warning: ignoring value of ssl.verifyhost * skipping SSL peer certificate verification * SSL connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 * Server certificate: * subject: CN=www.example.org,OU=Technology,O=Internet Corporation for Assigned Names and Numbers,L=Los Angeles,ST=California,C=US * start date: Nov 28 00:00:00 2018 GMT * expire date: Dec 02 12:00:00 2020 GMT * common name: www.example.org * issuer: CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US > GET / HTTP/1.1 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0 Host: www.example.com Accept: */* < HTTP/1.1 200 OK < Cache-Control: max-age=604800 < Content-Type: text/html; charset=UTF-8 < Date: Thu, 20 Dec 2018 12:07:18 GMT < Etag: "1541025663+ident" < Expires: Thu, 27 Dec 2018 12:07:18 GMT < Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT < Server: ECS (sjc/4E8B) < Vary: Accept-Encoding < X-Cache: HIT < Content-Length: 1270 < * Connection #10 to host www.example.com left intact [parallels@localhost zenglServerTest]$
上面的test_verbose.zl脚本将请求www.example.com相关的详细连接信息,都保存到了dump.txt文件中,在dump.txt中还可以看到请求头和响应头信息。
当前版本在curlEasyPerform模块函数中增加了&ptr参数(该参数是可选的,位于&size参数之后),可以获取到指向了抓取的数据的指针,利用该指针可以保存图像等二进制数据。相关测试脚本为:my_webroot/v0_16_0/test_download.zl,该脚本的代码如下:
use builtin, curl, request; def TRUE 1; def FALSE 0; rqtSetResponseHeader("Content-Type: text/html; charset=utf-8"); print 'curl version: ' + curlVersion(); curl_handle = curlEasyInit(); curlEasySetopt(curl_handle, 'URL', 'https://raw.githubusercontent.com/zenglong/zenglOX/master/screenshot/v302_1.jpg'); // curlEasySetopt(curl_handle, 'URL', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1544860168&di=62c1ebae354ee09f89093043177cfd2a&imgtype=jpg&er=1&src=http%3A%2F%2Fwinters.com%2Fwp-content%2Fuploads%2FPGTK315CM.jpg'); curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0'); curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE); curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE); curlEasySetopt(curl_handle, 'TIMEOUT', 30); ret = curlEasyPerform(curl_handle, &content, &size, &ptr); if(ret == 0) print 'size: ' + size; bltWriteFile('download.jpg', ptr, size); print 'write to <a href="download.jpg" target="_blank">download.jpg</a>'; else print 'error: ' + curlEasyStrError(ret); endif curlEasyCleanup(curl_handle); bltFree(ptr);
上面脚本中,在curlEasyPerform模块函数中使用了&ptr,将获取的图像数据的指针存储到ptr变量。接着就可以使用bltWriteFile模块函数,根据ptr指针和size图像的字节大小,将curl抓取到的图像的二进制数据写入到download.jpg文件中了。
该脚本的执行结果如下:
test_download.zl脚本通过curl下载图片,并保存为download.jpg文件
下载的download.jpg图片文件
当前版本还在builtin模块中,新增了bltFree和bltReadFile模块函数。这些模块函数相关的C代码位于module_builtin.c文件中:
/** * bltFree模块函数,用于释放掉zengl脚本中分配的指针资源 * 某些模块函数会分配一些指针以供调用者使用, * 例如,curl模块的curlEasyPerform模块函数,当提供了&ptr参数时, * 就会在内部分配一个指针,该指针指向的内存空间中存储了curl抓取到的数据,当抓取到的数据是jpg,png之类的图像等的二进制数据时, * 脚本中就可以利用指针将这些二进制数据写入文件,从而可以将curl下载的图像数据保存为文件, * 当这些指针不再需要使用时,可以用bltFree模块函数将其释放掉,如果没有使用该模块函数进行释放, * 在脚本退出时,zengl虚拟机也会自动清理这些指针 * * 该模块函数的第一个参数ptr表示需要释放的指针,在zengl脚本中,指针其实就是一个整数,该整数代表了指针所指向的内存地址 */ ZL_EXP_VOID module_builtin_free(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: bltFree(ptr): integer"); zenglApi_GetFunArg(VM_ARG,1,&arg); if(arg.type != ZL_EXP_FAT_INT) { zenglApi_Exit(VM_ARG,"the first argument [ptr] of bltFree must be integer"); } ZL_EXP_VOID * ptr = (ZL_EXP_VOID *)arg.val.integer; if(ptr != NULL) zenglApi_FreeMem(VM_ARG, ptr); zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, 0, 0); } /** * bltReadFile模块函数,根据文件名读取文件内容 * 该模块函数的第一个参数filename表示需要读取的文件的文件名,该文件名是一个相对于当前主执行脚本的路径, * 第二个&content参数必须是引用类型,用于存储读取的文件内容, * 第三个&size参数是可选的,当提供了该参数时,也必须是引用类型,用于存储文件的字节大小, * 该模块函数如果执行成功会返回整数0,执行失败则返回小于0的值,当返回值为-1时表示文件不存在,返回值为-2时表示无法打开文件, * 例如: use builtin; ret = bltReadFile('cookies.txt', &file_content, &file_size); if(ret == 0) print 'cookies.txt file size: ' + file_size; print 'file content: \n' + file_content; else print 'read cookies.txt failed, maybe the file does not exists, or open failed.'; endif * 上面这段代码会将cookies.txt文件的内容读取到file_content变量,同时将该文件的字节大小存储到file_size变量 */ ZL_EXP_VOID module_builtin_read_file(ZL_EXP_VOID * VM_ARG, ZL_EXP_INT argcount) { ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}}; if(argcount < 2) zenglApi_Exit(VM_ARG,"usage: bltReadFile(filename, &content[, &size]): integer"); zenglApi_GetFunArg(VM_ARG,1,&arg); if(arg.type != ZL_EXP_FAT_STR) { zenglApi_Exit(VM_ARG,"the first argument [filename] of bltReadFile must be string"); } char * filename = arg.val.str; ......................................................................... }
bltFree模块函数在之前提到过的test_download.zl脚本中就用到了,bltReadFile模块函数也在之前介绍过的test_cookiejar.zl文件中用到了。
该版本对进程名进行了调整,在主进程中包含了端口号,当前工作目录,所使用的配置文件等。子进程名称中也包含了父进程的进程ID。类似如下所示:
[parallels@localhost zenglServerTest]$ ps aux | grep zenglServer paralle+ 22252 0.0 0.1 139068 2348 ? Ss 15:31 0:00 zenglServer: master[8083] cwd:/media/psf/Home/zenglServerTest -c config.zl -l logfile paralle+ 22253 0.0 0.3 301524 6420 ? Sl 15:31 0:00 zenglServer: child(0) ppid:22252 paralle+ 22256 0.0 0.0 139068 1752 ? S 15:31 0:00 zenglServer: cleaner ppid:22252 .................................................. [parallels@localhost zenglServerTest]$
上面master[8083]表示该zenglServer所绑定的是8083端口,cwd:/media/psf/Home.... 表示zenglServer的当前工作目录。-c config.zl表示使用的配置文件是config.zl,该配置文件是相对于当前工作目录的,-l logfile表示所使用的日志文件是logfile,该日志文件名也是相对于当前工作目录的。
子进程中的ppid:22252,表示这些子进程的父进程的进程ID是22252。
为了修改进程名称,在根目录中专门增加了两个文件:zlsrv_setproctitle.c 和 zlsrv_setproctitle.h,这两个文件是专门用来设置进程名称的,zlsrv_setproctitle.c文件的代码如下:
/* * zlsrv_setproctitle.c * * Created on: Dec 16, 2018 * Author: zengl */ #include "zlsrv_setproctitle.h" #include <stdlib.h> #include <string.h> #define ZLSRV_ERR_ALLOC_FAILED "alloc failed for init setproctitle" extern char **environ; extern char ** zlsrv_main_argv; static char * st_zlsrv_argv_last; /** * 设置进程名称的初始化操作,在设置进程名称之前,需要先将环境变量拷贝到新建的堆空间中, * 并将environ数组中包含的旧的环境变量指针指向新的堆空间,在将环境变量拷贝出去后, * 就可以设置很长的进程名称了,设置进程名称的原理是将进程main函数的argv[0]指向的字符串进行修改, * 但是argv[0]指向的原字符串可能很短,这样当设置较长的进程名称时,就会溢出, * 而进程main函数的argv参数列表后面紧跟的是环境变量,因此就会将环境变量给破坏掉, * 所以在设置新的进程名称之前,需要先将环境变量拷贝一份,并将环境变量指针指向拷贝数据 * * 函数代码参考自nginx的代码,参考地址:https://github.com/firebase/nginx/blob/master/src/os/unix/ngx_setproctitle.c */ int zlsrv_init_setproctitle(char ** errorstr) { int i; size_t size; char * p; size = 0; for (i = 0; environ[i]; i++) { size += strlen(environ[i]) + 1; } p = malloc(size); if(p == NULL) { (*errorstr) = ZLSRV_ERR_ALLOC_FAILED; return -1; } st_zlsrv_argv_last = zlsrv_main_argv[0]; for (i = 0; zlsrv_main_argv[i]; i++) { if (st_zlsrv_argv_last == zlsrv_main_argv[i]) { st_zlsrv_argv_last = zlsrv_main_argv[i] + strlen(zlsrv_main_argv[i]) + 1; } } for (i = 0; environ[i]; i++) { if (st_zlsrv_argv_last == environ[i]) { size = strlen(environ[i]) + 1; st_zlsrv_argv_last = environ[i] + size; strncpy(p, environ[i], size); environ[i] = (char *) p; p += size; } } st_zlsrv_argv_last--; return 0; } /** * 在执行了上面的初始化操作后,就可以使用下面这个函数来设置当前进程的名称, * 设置进程名称时,只需将新的进程名称,拷贝到进程main函数的argv[0]所指向的内存空间中即可, * 在设置了新的进程名称后,还将进程名称之后的原有的残留数据给清空了(包括参数列表后的环境变量,环境变量已经在初始化时拷贝出去了,所以没有影响), * 防止ps在显示进程名称时,将进程名之后的原来残留的数据当作参数列表给显示出来。 */ void zlsrv_setproctitle(char * title) { char * p; size_t size; zlsrv_main_argv[1] = NULL; p = zlsrv_main_argv[0]; size = strlen(title); if(size > (st_zlsrv_argv_last - p)) size = st_zlsrv_argv_last - p; strncpy(p, title, size); p += size; if(st_zlsrv_argv_last - p) { memset(p, '\0', (st_zlsrv_argv_last - p)); } }
当前版本的zenglServer在启动时,还可以使用-l的命令行参数来指定日志文件名:
[parallels@localhost zenglServerTest]$ ./zenglServer -l logfile_test [parallels@localhost zenglServerTest]$ ps aux | grep zenglServer paralle+ 25592 0.0 0.1 139068 2344 ? Ss 21:22 0:00 zenglServer: master[8083] cwd:/media/psf/Home/zenglServerTest -c config.zl -l logfile_test paralle+ 25593 0.0 0.0 155460 1756 ? Sl 21:22 0:00 zenglServer: child(0) ppid:25592 paralle+ 25596 0.0 0.0 139068 1756 ? S 21:22 0:00 zenglServer: cleaner ppid:25592 paralle+ 25604 0.0 0.0 112720 988 pts/1 S+ 21:22 0:00 grep --color=auto zenglServer [parallels@localhost zenglServerTest]$ kill 25592 [parallels@localhost zenglServerTest]$ tail -n 20 logfile_test 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 bind done accept sem initialized. process_max_open_fd_num: 1024 Master: Spawning child(0) [pid 25593] Master: Spawning cleaner [pid 25596] epoll max fd count : 896 ------------ cleaner sleep begin: 1545312169 Termination signal received! Killing children.. All children reaped, shutting down. ------------ remove shm number: 0 closed accept_sem shutdowned server socket closed server socket =================================== [parallels@localhost zenglServerTest]$
上面通过-l logfile_test指定了zenglServer所使用的日志文件为logfile_test。
以上就是当前版本的相关内容,就到这里,休息,休息一下。
梦想只要能持久,就能成为现实。我们不就是生活在梦想中的吗?
—— 阿尔弗雷德·丁尼生