当前版本的zenglServer使用v1.8.0版本的zengl语言库,该版本的语言库增加了zenglApi_CacheMemData和zenglApi_ReUseCacheMemData接口。在config.zl配置文件中,添加了zengl_cache_enable的配置,表示是否开启zengl脚本的编译缓存...

    页面导航: 项目下载地址:

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

zenglServer v0.10.0:

    当前版本的zenglServer使用v1.8.0版本的zengl语言库,该版本的语言库增加了zenglApi_CacheMemData和zenglApi_ReUseCacheMemData接口。

    zenglApi_CacheMemData接口用于将编译器和解释器中主要的内存编译数据缓存起来,缓存的数据可以存储到文件或者别的地方,之后就可以利用缓存起来的数据来跳过编译过程,直接执行虚拟汇编指令。缓存数据只可以用于当前机器。

    zenglApi_ReUseCacheMemData接口则用于重利用缓存数据,从而跳过编译过程。

    在main.c文件中,就会利用上面这两个接口来完成缓存编译数据,和重利用编译数据的操作:

..............................................................................

static long config_zengl_cache_enable; // 是否开启zengl脚本的编译缓存

..............................................................................

/**
 * 根据full_path脚本路径,得到最终要生成的缓存文件的路径信息
 */
static void main_get_zengl_cache_path(char * cache_path, int cache_path_size, char * full_path)
{
	char fullpath_md5[33];
	char cache_prefix[20] = {0};
	const char * cache_path_prefix = "zengl/caches/"; // 缓存文件都放在zengl/caches目录中
	int append_length;
	main_compute_md5(fullpath_md5, full_path, ZL_EXP_TRUE, ZL_EXP_TRUE); // 将full_path进行md5编码
	// 在缓存路径前面加上zengl版本号和指针长度,不同的zengl版本生成的缓存有可能会不一样,另外,32位和64位环境下生成的内存缓存数据也是不一样的
	// 32位系统中生成的缓存数据放到64位中运行,或者反过来,都会报内存相关的错误
	sprintf(cache_prefix, "%d_%d_%d_%ld_", ZL_EXP_MAJOR_VERSION, ZL_EXP_MINOR_VERSION, ZL_EXP_REVISION, sizeof(char *));
	append_length = main_full_path_append(cache_path, 0, cache_path_size, (char *)cache_path_prefix);
	append_length += main_full_path_append(cache_path, append_length, cache_path_size, cache_prefix);
	append_length += main_full_path_append(cache_path, append_length, cache_path_size, fullpath_md5);
	cache_path[append_length] = '\0';
}

/**
 * 尝试重利用full_path脚本文件对应的缓存数据,cache_path表示缓存数据所在的文件路径
 * 如果缓存文件不存在,则会重新生成缓存文件,如果full_path脚本文件内容发生了改变或者其加载的脚本文件内容发生了改变,也会重新生成缓存
 * 外部调用者通过is_reuse_cache变量的值来判断是否需要生成缓存文件,如果is_reuse_cache为ZL_EXP_FALSE,就表示没有重利用缓存,则需要生成缓存文件
 * 如果is_reuse_cache为ZL_EXP_TRUE,则说明重利用了缓存,不需要再生成缓存文件了
 */
static void main_try_to_reuse_zengl_cache(ZL_EXP_VOID * VM, char * cache_path, char * full_path, ZL_EXP_BOOL * is_reuse_cache)
{
	FILE * ptr_fp;
	ZL_EXP_VOID * cachePoint;
	ZENGL_EXPORT_API_CACHE_TYPE * api_cache;
	ZL_EXP_LONG offset, cache_mtime, file_mtime;
	ZL_EXP_BYTE * mempoolPtr;
	ZL_EXP_CHAR ** filenames, * filename;
	ZL_EXP_INT cacheSize, i;
	struct stat stat_result;
	(* is_reuse_cache) = ZL_EXP_FALSE;
	if(stat(cache_path, &stat_result)==0) { // 获取缓存文件的修改时间
		cache_mtime = (ZL_EXP_LONG)stat_result.st_mtime;
	}
	else { // 获取文件的状态信息失败,可能缓存文件不存在,需要重新编译生成缓存,直接返回
		write_to_server_log_pipe(WRITE_TO_PIPE, "can not stat cache file: \"%s\", maybe no such cache file [recompile]\n", cache_path);
		return ;
	}
	if(stat(full_path, &stat_result)==0) { // 获取主执行脚本的修改时间
		file_mtime = (ZL_EXP_LONG)stat_result.st_mtime;
		if(file_mtime >= cache_mtime) { // 如果主执行脚本的修改时间大于等于缓存数据的修改时间,则说明主执行脚本的内容发生了改变,需要重新编译生成新的缓存
			write_to_server_log_pipe(WRITE_TO_PIPE, "\"%s\" mtime:%ld [changed] [recompile]\n", full_path, file_mtime);
			return;
		}
	}
	else { // 主执行脚本不存在,直接返回
		write_to_server_log_pipe(WRITE_TO_PIPE, "warning stat script file: \"%s\" failed, maybe no such file! [recompile]\n", full_path);
		return ;
	}
	// 打开缓存文件
	if((ptr_fp = fopen(cache_path, "rb")) == NULL) {
		write_to_server_log_pipe(WRITE_TO_PIPE, "no cache file: \"%s\" [recompile]\n", cache_path);
		return ;
	}
	flock(ptr_fp->_fileno, LOCK_SH); // 加文件共享锁,如果有进程在修改缓存内容的话,所有读缓存的进程都会等待写入完成,再执行读操作
	fseek(ptr_fp,0L,SEEK_END);
	cacheSize = ftell(ptr_fp); // 得到缓存数据的大小
	fseek(ptr_fp,0L,SEEK_SET);
	cachePoint = malloc(cacheSize); // 根据缓存大小分配堆空间,先将缓存数据读取到该堆内存中
	if(fread(cachePoint, cacheSize, 1, ptr_fp) != 1) { // 读取缓存数据
		write_to_server_log_pipe(WRITE_TO_PIPE, "read cache file \"%s\" failed [recompile]\n", cache_path);
		goto end;
	}
	api_cache = (ZENGL_EXPORT_API_CACHE_TYPE *)cachePoint;
	if(api_cache->signer != ZL_EXP_API_CACHE_SIGNER) { // 根据缓存签名判断是否是有效的缓存数据
		write_to_server_log_pipe(WRITE_TO_PIPE, "invalid cache file \"%s\" [recompile]\n", cache_path);
		goto end;
	}
	mempoolPtr = ((ZL_EXP_BYTE *)cachePoint + api_cache->mempoolOffset);
	offset = (ZL_EXP_LONG)api_cache->filenames;
	filenames = (ZL_EXP_CHAR **)(mempoolPtr + offset - 1);
	if(api_cache->filenames_count > 0) {
		// 循环判断加载的脚本文件的内容是否发生了改变,如果改变了,则需要重新编译生成新的缓存
		for(i=0; i < api_cache->filenames_count; i++) {
			offset = (ZL_EXP_LONG)(filenames[i]);
			filename = (ZL_EXP_CHAR *)(mempoolPtr + offset - 1);
			if(stat(filename, &stat_result)==0) {
				file_mtime = (ZL_EXP_LONG)stat_result.st_mtime;
				if(file_mtime >= cache_mtime){
					write_to_server_log_pipe(WRITE_TO_PIPE, "\"%s\" mtime:%ld [changed] [recompile]\n", filename, file_mtime);
					goto end;
				}
			}
			else {
				write_to_server_log_pipe(WRITE_TO_PIPE, " stat \"%s\" failed [recompile]\n", filename);
				goto end;
			}
		}
	}
	// 通过zenglApi_ReUseCacheMemData接口函数,将编译好的缓存数据加载到编译器和解释器中,这样就可以跳过编译过程,直接运行
	if(zenglApi_ReUseCacheMemData(VM, cachePoint, cacheSize) == -1) {
		write_to_server_log_pipe(WRITE_TO_PIPE, "reuse cache file \"%s\" failed: %s [recompile]\n", cache_path, zenglApi_GetErrorString(VM));
		goto end;
	}
	(* is_reuse_cache) = ZL_EXP_TRUE;
	write_to_server_log_pipe(WRITE_TO_PIPE, "reuse cache file: \"%s\" mtime:%ld\n", cache_path, cache_mtime);
end:
	fclose(ptr_fp);
	flock(ptr_fp->_fileno, LOCK_UN); // 解锁
	free(cachePoint);
}

/**
 * 在编译执行结束后,生成缓存数据并写入缓存文件
 */
static void main_write_zengl_cache_to_file(ZL_EXP_VOID * VM, char * cache_path)
{
	FILE * ptr_fp;
	ZL_EXP_VOID * cachePoint;
	ZL_EXP_INT cacheSize;
	// 通过zenglApi_CacheMemData接口函数,将编译器和解释器中的主要的内存数据缓存到cachePoint对应的内存中
	if(zenglApi_CacheMemData(VM, &cachePoint, &cacheSize) == -1) {
		write_to_server_log_pipe(WRITE_TO_PIPE, "write zengl cache to file \"%s\" failed: %s\n", cache_path,zenglApi_GetErrorString(VM));
		return;
	}

	// 打开cache_path对应的缓存文件
	if((ptr_fp = fopen(cache_path, "wb")) == NULL) {
		write_to_server_log_pipe(WRITE_TO_PIPE, "write zengl cache to file \"%s\" failed: open failed\n", cache_path);
		return;
	}
	flock(ptr_fp->_fileno, LOCK_EX); // 写入缓存数据之前,先加入互斥锁,让所有读进程等待写入完成
	// 将缓存数据写入缓存文件
	if( fwrite(cachePoint, cacheSize, 1, ptr_fp) != 1)
		write_to_server_log_pipe(WRITE_TO_PIPE, "write zengl cache to file \"%s\" failed: write failed\n", cache_path);
	else
		write_to_server_log_pipe(WRITE_TO_PIPE, "write zengl cache to file \"%s\" success \n", cache_path);
	fclose(ptr_fp);
	flock(ptr_fp->_fileno, LOCK_UN); // 解锁
}

..............................................................................

/**
 * zenglServer启动时会执行的入口函数
 */
int main(int argc, char * argv[])
{
	.......................................................
	// 获取配置文件中设置的zengl_cache_enable即是否开启zengl脚本的编译缓存
	if(zenglApi_GetValueAsInt(VM,"zengl_cache_enable", &config_zengl_cache_enable) < 0)
		config_zengl_cache_enable = ZL_EXP_FALSE;
	.......................................................
	// 将远程调试相关的配置,以及是否开启zengl脚本的编译缓存的配置,记录到日志中
	write_to_server_log_pipe(WRITE_TO_LOG, "remote_debug_enable: %s remote_debugger_ip: %s remote_debugger_port: %ld"
			" zengl_cache_enable: %s\n",
			config_remote_debug_enable ? "True" : "False",
			config_remote_debugger_ip,
			config_remote_debugger_port,
			config_zengl_cache_enable ? "True" : "False");
	.......................................................
}

..............................................................................

/**
 * 当线程读取到客户端的完整的请求数据后,就会执行下面这个函数,去处理该请求,
 * 并将处理的结果写入到输出缓存,函数返回后,线程会将输出缓存里的数据传递给客户端,
 * 当输出缓存中的数据比较多时,线程就需要分多次进行传输(通过检测EPOLLOUT事件来实现多次传输,
 * 当收到EPOLLOUT事件时,就说明该事件对应的客户端连接可以继续发送数据了)
 */
static int routine_process_client_socket(CLIENT_SOCKET_LIST * socket_list, int lst_idx)
{
	.......................................................
	// 如果要访问的文件是以.zl结尾的,就将该文件当做zengl脚本来进行编译执行
	if(full_length > 3 && (stat(full_path, &filestatus) == 0) && (strncmp(full_path + (full_length - 3), ".zl", 3) == 0)) {
		.......................................................
		char cache_path[80];
		ZL_EXP_BOOL is_reuse_cache;
		// 如果开启了zengl脚本的编译缓存,则尝试重利用缓存数据
		if(config_zengl_cache_enable) {
			// 根据脚本文件名得到缓存文件的路径信息
			main_get_zengl_cache_path(cache_path, sizeof(cache_path), full_path);
			// 尝试重利用缓存数据
			main_try_to_reuse_zengl_cache(VM, cache_path, full_path, &is_reuse_cache);
		}
		if(zenglApi_Run(VM, full_path) == -1) //编译执行zengl脚本
		{
			// 如果执行失败,则显示错误信息,并抛出500内部错误给客户端
			write_to_server_log_pipe(WRITE_TO_PIPE, "zengl run <%s> failed: %s\n",full_path, zenglApi_GetErrorString(VM));
			client_socket_list_append_send_data(socket_list, lst_idx, "HTTP/1.1 500 Internal Server Error\r\n", 36);
			dynamic_string_append(&my_data.response_body, "500 Internal Server Error", 25, 200);
			status_code = 500;
		}
		else {
			// 如果开启了编译缓存,那么在没有重利用缓存数据时(例如缓存文件不存在,或者原脚本内容发生的改变等),就生成新的缓存数据,并将其写入缓存文件中
			if(config_zengl_cache_enable && !is_reuse_cache)
				main_write_zengl_cache_to_file(VM, cache_path);
			if(!(my_data.response_header.count > 0 && strncmp(my_data.response_header.str, "HTTP/", 5) == 0)) {
				client_socket_list_append_send_data(socket_list, lst_idx, "HTTP/1.1 200 OK\r\n", 17);
				if(my_data.response_header.count == 0 ||
				  !strcasestr(my_data.response_header.str, "content-type:")) { // 没有定义过Content-Type响应头,则默认输出text/html
					client_socket_list_append_send_data(socket_list, lst_idx, "Content-Type: text/html\r\n", 25);
				}
			}
			else
				is_custom_status_code = ZL_EXP_TRUE; // 用户自定义了http状态码
		}
		.......................................................
	}
}


    在config.zl配置文件中,添加了zengl_cache_enable的配置,表示是否开启zengl脚本的编译缓存:

def TRUE 1;
def FALSE 0;

........................................................

zengl_cache_enable = FALSE; // 是否开启zengl脚本的编译缓存,默认为FALSE即不开启,设置为TRUE可以开启编译缓存


    在开启了编译缓存后(修改配置需要重新运行zenglServer),当脚本对应的缓存文件不存在时,在编译执行完脚本后会自动生成缓存文件,并在日志中看到write zengl cache to file...的信息:

zengl@zengl-ubuntu:~/zenglServer$ tail -f logfile 
..........................................................
-----------------------------------
Sun Apr  1 15:40:57 2018
recv [client_socket_fd:9] [lst_idx:0] [pid:5777] [tid:5780]:

request header: Host: 192.168.1.105:8083 | User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 | Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 | Accept-Encoding: gzip, deflate | Connection: keep-alive | Upgrade-Insecure-Requests: 1 | 

url: /v0_8_0/test.zl
url_path: /v0_8_0/test.zl
full_path: my_webroot/v0_8_0/test.zl
can not stat cache file: "zengl/caches/1_8_0_8_68bf762f1d8a4e321fe71affb3b681ab", maybe no such cache file [recompile]
write zengl cache to file "zengl/caches/1_8_0_8_68bf762f1d8a4e321fe71affb3b681ab" success 
status: 200, content length: 918
response header: HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 918
Connection: Closed
Server: zenglServer
free socket_list[0]/list_cnt:0 epoll_fd_add_count:0 pid:5777 tid:5780


    如果缓存文件已存在,就会自动重利用缓存的编译数据,跳过编译过程,并在日志中看到reuse cache file: ...的信息:

zengl@zengl-ubuntu:~/zenglServer$ tail -f logfile 
..........................................................
-----------------------------------
Sun Apr  1 15:48:15 2018
recv [client_socket_fd:9] [lst_idx:0] [pid:5777] [tid:5780]:

request header: Host: 192.168.1.105:8083 | User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 | Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 | Accept-Encoding: gzip, deflate | Connection: keep-alive | Upgrade-Insecure-Requests: 1 | Cache-Control: max-age=0 | 

url: /v0_8_0/test.zl
url_path: /v0_8_0/test.zl
full_path: my_webroot/v0_8_0/test.zl
reuse cache file: "zengl/caches/1_8_0_8_68bf762f1d8a4e321fe71affb3b681ab" mtime:1522568457
status: 200, content length: 918
response header: HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 918
Connection: Closed
Server: zenglServer
free socket_list[0]/list_cnt:0 epoll_fd_add_count:0 pid:5777 tid:5780


    如果脚本文件内容发生了改变或者其加载的脚本文件内容发生了改变,也会重新生成缓存:

zengl@zengl-ubuntu:~/zenglServer$ tail -f logfile 
..........................................................
-----------------------------------
Sun Apr  1 15:53:37 2018
recv [client_socket_fd:10] [lst_idx:0] [pid:5777] [tid:5780]:

request header: Host: 192.168.1.105:8083 | User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 | Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 | Accept-Encoding: gzip, deflate | Connection: keep-alive | Upgrade-Insecure-Requests: 1 | Cache-Control: max-age=0 | 

url: /v0_8_0/test.zl
url_path: /v0_8_0/test.zl
full_path: my_webroot/v0_8_0/test.zl
"my_webroot/v0_8_0/test.zl" mtime:1522569211 [changed] [recompile]
write zengl cache to file "zengl/caches/1_8_0_8_68bf762f1d8a4e321fe71affb3b681ab" success 
status: 200, content length: 946
response header: HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 946
Connection: Closed
Server: zenglServer
free socket_list[0]/list_cnt:0 epoll_fd_add_count:0 pid:5777 tid:5780


    上面当my_webroot/v0_8_0/test.zl脚本的内容发生改变后,再次运行时,日志中就会显示[changed],并且[recompile]重新编译脚本,以及生成新的缓存文件。

    缓存文件会生成在zengl/caches目录中,文件名是由zengl语言库的版本号,内存指针的长度以及脚本文件路径的md5值组成的。

    以上就是当前版本的相关内容,与zengl v1.8.0相关的内容,请参考zengl编程语言栏目中的“zengl v1.8.0 缓存内存中的编译数据,跳过编译过程”对应的文章。

结束语:

    “我想我们不会再见面了”     “这辈子而已”

——  《暗战》
 
上下篇

下一篇: zenglServer v0.10.1 添加bltInt,bltFloat,bltHtmlEscape模块函数,使用v1.8.1版本的zengl语言库

上一篇: zenglServer v0.9.0 pydebugger 远程调试

相关文章

zenglServer v0.3.0 mysql模块

zenglServer v0.13.0 目录入口文件以及模板路径调整

zenglServer v0.8.0-v0.8.1

zenglServer v0.18.0 直接在命令行中执行脚本

zenglServer v0.25.0 使用v1.9.0版本的zengl语言库,增加backlog及timezone配置,增加bltSetTimeZone模块函数

zenglServer v0.19.0 增加redis缓存相关的模块