从v0.19.0版本开始,在编译时,可以添加redis模块,从而可以执行redis缓存相关的工作。只要在make命令后面加入USE_REDIS=yes即可。

    页面导航:

项目下载地址:

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

zenglServer v0.19.0:

    从v0.19.0版本开始,在编译时,可以添加redis模块,从而可以执行redis缓存相关的工作。只要在make命令后面加入USE_REDIS=yes即可。

    当然,要使用redis模块,前提是系统中安装了hiredis相关的底层库。

    如果是ubuntu系统,可以通过 sudo apt-get install libhiredis-dev 来安装hiredis相关的库和开发头文件等。

    如果是centos系统,则可以通过 yum install hiredis-devel 来安装相关的底层库。

    还可以使用源码编译方式来安装hiredis库:

curl -L -o hiredis.zip https://github.com/redis/hiredis/archive/v0.13.3.zip
unzip -d ./hiredis hiredis.zip
cd hiredis/hiredis-0.13.3/
make -j
sudo make install
sudo ldconfig

    使用源码编译安装时,还需要通过PKG_CONFIG_PATH环境变量来指定make install时,安装的hiredis.pc的位置(下面假设该文件位于/usr/local/lib/pkgconfig/目录中):

export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig/

    要同时使用mysql,magick,pcre,curl,以及redis模块,可以使用 make USE_MYSQL=yes USE_MAGICK=6 USE_PCRE=yes USE_CURL=yes USE_REDIS=yes 命令。

[root@localhost zenglServerTest]# make USE_MYSQL=yes USE_MAGICK=6 USE_PCRE=yes USE_CURL=yes USE_REDIS=yes
............................................................
gcc -g3 -ggdb -O0 -std=c99 main.c http_parser.c module_request.c module_builtin.c module_session.c dynamic_string.c multipart_parser.c resources.c client_socket_list.c json.c randutils.c md5.c debug.c zlsrv_setproctitle.c main.h http_parser.h common_header.h module_request.h module_builtin.h module_session.h dynamic_string.h multipart_parser.h resources.h client_socket_list.h json.h randutils.h md5.h debug.h zlsrv_setproctitle.h module_mysql.c module_mysql.h  module_magick.c module_magick.h module_pcre.c module_pcre.h module_curl.c module_curl.h module_redis.c module_redis.h zengl/linux/zengl_exportfuns.h  -o zenglServer zengl/linux/libzengl.a crustache/libcrustache.a   -lpthread -lm -DUSE_MYSQL `mysql_config --cflags --libs`  -D USE_MAGICK=6 `pkg-config --cflags --libs Wand` -DUSE_PCRE `pcre-config --cflags --libs` -DUSE_CURL `curl-config --cflags --libs` -DUSE_REDIS `pkg-config --cflags --libs hiredis`

mysql module is enabled!!!
magick module is enabled!!!
pcre module is enabled!!!
curl module is enabled!!!
redis module is enabled!!!
[root@localhost zenglServerTest]# 

redisConnect模块函数:

    redisConnect模块函数,可以根据指定的ip地址,端口号等参数连接对应的redis服务器。相关的C源码位于module_redis.c文件中:

/**
 * redisConnect模块函数,根据指定的ip地址,端口号等参数连接对应的redis服务器
 * 第一个参数ip表示需要连接的redis服务器所在的ip地址或者主机名
 * 第二个参数port表示需要连接的redis服务器的端口号
 * 第三个参数context必须是引用类型,用于存储redis连接相关的上下文指针,通过该指针可以向redis服务器发送命令等
 * 第四个参数error是可选参数,也必须是引用类型,当连接发生错误时,会将错误信息存储到该参数中
 * 第五个参数timeout也是可选参数,表示连接超时时间,以秒为单位
 * 如果该模块函数连接成功,会返回1,连接失败则返回0,并将连接失败的原因存储到第四个error参数中
 *
 * 例如:
 * use builtin, redis;
 *
 * fun exit(error)
		print error;
		bltExit();
   endfun

   if(!redisConnect("127.0.0.1", 6379, &con, &error, 30))
		exit(error);
   endif

   上面这段代码会尝试连接ip地址为127.0.0.1,端口为6379的redis服务器,并设置了30秒的连接超时时间
   如果连接成功,则会将连接相关的上下文指针存储到con变量,如果连接失败,则会将错误信息存储到error变量
 */
ZL_EXP_VOID module_redis_connect(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 3)
		zenglApi_Exit(VM_ARG,"usage: redisConnect(ip, port, &context[, &error[, timeout]])");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_STR) {
		zenglApi_Exit(VM_ARG,"the first argument [ip] of redisConnect must be string");
	}
	char * ip = arg.val.str;
	zenglApi_GetFunArg(VM_ARG,2,&arg);
	int port = 0;
	if(arg.type == ZL_EXP_FAT_INT) {
		port = (unsigned int)arg.val.integer;
	}
	else {
		zenglApi_Exit(VM_ARG,"the second argument [port] of redisConnect must be integer");
	}
	time_t timeout = 0;
	detect_arg_is_address_type(VM_ARG, 3, &arg, "third", "context", "redisConnect");
	if(argcount > 3) {
		detect_arg_is_address_type(VM_ARG, 4, &arg, "fourth", "error", "redisConnect");
		if(argcount > 4) {
			zenglApi_GetFunArg(VM_ARG,5,&arg);
			if(arg.type != ZL_EXP_FAT_INT) {
				zenglApi_Exit(VM_ARG,"the fifth argument [timeout] of redisConnect must be integer");
			}
			timeout = (time_t)arg.val.integer;
		}
	}
	redisContext * context = NULL;
	if(!timeout) {
		context = redisConnect((const char *)ip, port);
	}
	else {
		struct timeval tv = {0};
		tv.tv_sec = timeout;
		context = redisConnectWithTimeout((const char *)ip, port, tv);
	}
	ZL_EXP_LONG retval = ZL_EXP_TRUE;
	if(context == NULL || context->err) {
		if(argcount > 3) {
			DYNAMIC_STRING errstr = {0};
			if(context) {
				const char * err_const = "redisConnect error: ";
				dynamic_string_append(&errstr, (char *)err_const, strlen(err_const), 100);
				dynamic_string_append(&errstr, (char *)context->errstr, strlen(context->errstr), 100);
			}
			else {
				const char * err_const = "redisConnect can't allocate redis context";
				dynamic_string_append(&errstr, (char *)err_const, strlen(err_const), 100);
			}
			set_arg_value(VM_ARG, 4, ZL_EXP_FAT_STR, errstr.str, 0);
			dynamic_string_free(&errstr);
		}
		retval = ZL_EXP_FALSE;
		redisFree(context);
		context = NULL;
	}
	else {
		MAIN_DATA * my_data = zenglApi_GetExtraData(VM_ARG, "my_data");
		int ret_code = resource_list_set_member(&(my_data->resource_list), context, module_redis_free_context_resource_callback);
		if(ret_code != 0) {
			zenglApi_Exit(VM_ARG, "redisConnect add resource to resource_list failed, resource_list_set_member error code:%d", ret_code);
		}
	}
	set_arg_value(VM_ARG, 3, ZL_EXP_FAT_INT, NULL, (ZL_EXP_LONG)context);
	zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, retval, 0);
}

redisCommand模块函数:

    redisCommand模块函数,可以向redis服务器发送命令,并获取redis命令的执行结果。该模块函数的相关C源码也位于module_redis.c文件里:

/**
 * redisCommand模块函数,向redis服务器发送命令,并获取redis命令的执行结果
 * 第一个参数context,是通过redisConnect模块函数获取到的和redis连接相关的上下文指针
 * 第二个参数command,表示需要发送的redis命令,可以是字符串的形式,也可以是数组的形式
 * 第三个参数result必须是引用类型,用于存储redis命令的执行结果
 * 第四个参数is_null是可选参数,也必须是引用类型,用于表示执行结果是否为空
 * 第五个参数error也是可选参数,也必须是引用类型,当命令执行出错时,会将出错信息存储到该参数中
 * 第六个参数array_assoc也是可选参数,用于表示如果结果是数组类型的话,是否将其转换为哈希数组(使用字符串作为数组成员的key)
 * 如果redis命令执行成功,该模块函数会返回1,执行失败则返回0
 *
 * 例如:
	use builtin, redis;
	def TRUE 1;
	def FALSE 0;

	fun exit(error)
		print error;
		bltExit();
	endfun

	// 连接redis服务器
	if(!redisConnect("127.0.0.1", 6379, &con, &error, 30))
		exit(error);
	endif

	// 向redis服务器发送 get name 命令
	if(!redisCommand(con, "get name", &result, &is_null, &error))
		exit(error);
	endif

	// 判断命令的执行结果是否为空,不为空则将结果打印出来
	if(is_null)
		print '*** null ***';
	else
		print result;
	endif

	// 以数组的形式发送命令:hset hash2 testname "say \"hello world!\""
	if(!redisCommand(con, bltArray('hset', 'hash2', 'testname', 'say "hello world!"'), &result, &is_null, &error))
		exit(error);
	else
		print result;
	endif

	// 执行命令 hgetall hash2 ,并将结果转为哈希数组
	if(!redisCommand(con, "hgetall hash2", &result, &is_null, &error, TRUE))
		exit(error);
	endif

	if(is_null)
		print '*** null ***';
	else
		print result;
		// 循环将哈希数组中每个成员的键名和值打印出来
		for(i=0;bltIterArray(result,&i,&k,&v);)
			print k + ':' + v;
		endfor
	endif
 */
ZL_EXP_VOID module_redis_command(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 3)
		zenglApi_Exit(VM_ARG,"usage: redisCommand(context, command, &result[, &is_null[, &error[, array_assoc]]])");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_INT) {
		zenglApi_Exit(VM_ARG,"the first argument [context] of redisCommand must be integer");
	}
	redisContext * context = (redisContext *)arg.val.integer;
	MAIN_DATA * my_data = zenglApi_GetExtraData(VM_ARG, "my_data");
	if(!is_valid_redis_context(&(my_data->resource_list), context)) {
		zenglApi_Exit(VM_ARG,"redisCommand runtime error: invalid context");
	}
	zenglApi_GetFunArg(VM_ARG,2,&arg);
	char * command = NULL;
	ZENGL_EXPORT_MEMBLOCK command_memblock = {0};
	if(arg.type == ZL_EXP_FAT_STR) {
		command = arg.val.str;
	}
	else if(arg.type == ZL_EXP_FAT_MEMBLOCK) {
		command_memblock = arg.val.memblock;
	}
	else {
		zenglApi_Exit(VM_ARG,"the second argument [command] of redisCommand must be string or array");
	}
	ZL_EXP_LONG array_assoc = ZL_EXP_FALSE;
	detect_arg_is_address_type(VM_ARG, 3, &arg, "third", "result", "redisCommand");
	if(argcount > 3) {
		detect_arg_is_address_type(VM_ARG, 4, &arg, "fourth", "is_null", "redisCommand");
		if(argcount > 4) {
			detect_arg_is_address_type(VM_ARG, 5, &arg, "fifth", "error", "redisCommand");
			if(argcount > 5) {
				zenglApi_GetFunArg(VM_ARG,6,&arg);
				if(arg.type != ZL_EXP_FAT_INT) {
					zenglApi_Exit(VM_ARG,"the sixth argument [array_assoc] of redisCommand must be integer");
				}
				array_assoc = arg.val.integer;
			}
		}
	}
	redisReply * reply = NULL;
	ZL_EXP_INT memblock_valid_count = 0;
	if(command != NULL) {
		reply = redisCommand(context, command);
	}
	else {
		reply = st_redis_set_command_by_array(VM_ARG, context, command_memblock, &memblock_valid_count);
	}
	ZL_EXP_LONG retval = ZL_EXP_TRUE;
	ZL_EXP_LONG is_null = ZL_EXP_FALSE;
	if(reply == NULL) {
		char error_str[300] = {0};
		if(argcount > 4) {
			if(memblock_valid_count <= 0) {
				snprintf(error_str, 300, "redisCommand error: command array invalid count: %d", memblock_valid_count);
				set_arg_value(VM_ARG, 5, ZL_EXP_FAT_STR, (char *)error_str, 0);
			}
			else {
				snprintf(error_str, 300, "redisCommand error: reply is null");
				set_arg_value(VM_ARG, 5, ZL_EXP_FAT_STR, (char *)error_str, 0);
			}
		}
		set_arg_value(VM_ARG, 3, ZL_EXP_FAT_INT, NULL, 0);
		retval = ZL_EXP_FALSE;
	}
	else if(reply->type == REDIS_REPLY_ERROR) {
		if(argcount > 4) {
			DYNAMIC_STRING errstr = {0};
			const char * err_const = "redisCommand error: ";
			dynamic_string_append(&errstr, (char *)err_const, strlen(err_const), 100);
			dynamic_string_append(&errstr, reply->str, strlen(reply->str), 100);
			set_arg_value(VM_ARG, 5, ZL_EXP_FAT_STR, errstr.str, 0);
			dynamic_string_free(&errstr);
		}
		set_arg_value(VM_ARG, 3, ZL_EXP_FAT_INT, NULL, 0);
		retval = ZL_EXP_FALSE;
	}
	else if(reply->type == REDIS_REPLY_STRING || reply->type == REDIS_REPLY_STATUS) {
		set_arg_value(VM_ARG, 3, ZL_EXP_FAT_STR, reply->str, 0);
	}
	else if(reply->type == REDIS_REPLY_INTEGER) {
		set_arg_value(VM_ARG, 3, ZL_EXP_FAT_INT, NULL, (ZL_EXP_LONG)reply->integer);
	}
	else if(reply->type == REDIS_REPLY_NIL) {
		set_arg_value(VM_ARG, 3, ZL_EXP_FAT_INT, NULL, 0);
		is_null = ZL_EXP_TRUE;
	}
	else if(reply->type == REDIS_REPLY_ARRAY) {
		ZENGL_EXPORT_MEMBLOCK memblock = {0};
		ZENGL_EXPORT_MOD_FUN_ARG memblock_arg = {ZL_EXP_FAT_NONE,{0}};
		if(zenglApi_CreateMemBlock(VM_ARG,&memblock,0) == -1) {
			zenglApi_Exit(VM_ARG,zenglApi_GetErrorString(VM_ARG));
		}
		int step = 1;
		if(array_assoc)
			step = 2;
		for(int i = 0; i < reply->elements; i += step) {
			int n = i;
			if(array_assoc)
				n = i + 1;
			switch(reply->element[n]->type) {
			case REDIS_REPLY_STRING:
			case REDIS_REPLY_STATUS:
				memblock_arg.type = ZL_EXP_FAT_STR;
				memblock_arg.val.str = reply->element[n]->str;
				break;
			case REDIS_REPLY_INTEGER:
				memblock_arg.type = ZL_EXP_FAT_INT;
				memblock_arg.val.integer = (ZL_EXP_LONG)reply->element[n]->integer;
				break;
			default:
				memblock_arg.type = ZL_EXP_FAT_INT;
				memblock_arg.val.integer = 0;
				break;
			}
			if(array_assoc && reply->element[i]->type == REDIS_REPLY_STRING) {
				zenglApi_SetMemBlockByHashKey(VM_ARG, &memblock, reply->element[i]->str, &memblock_arg);
			}
			else {
				zenglApi_SetMemBlock(VM_ARG, &memblock, (i+1), &memblock_arg);
			}
		}
		arg.type = ZL_EXP_FAT_MEMBLOCK;
		arg.val.memblock = memblock;
		zenglApi_SetFunArg(VM_ARG,3,&arg);
	}
	else {
		if(argcount > 4) {
			char error_str[300] = {0};
			snprintf(error_str, 300, "redisCommand error: invalid reply type: %d", reply->type);
			set_arg_value(VM_ARG, 5, ZL_EXP_FAT_STR, (char *)error_str, 0);
		}
		set_arg_value(VM_ARG, 3, ZL_EXP_FAT_INT, NULL, 0);
		retval = ZL_EXP_FALSE;
	}
	if(argcount > 3) {
		set_arg_value(VM_ARG, 4, ZL_EXP_FAT_INT, NULL, is_null);
	}
	freeReplyObject(reply);
	zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, retval, 0);
}

redisFree模块函数:

    redisFree模块函数,用于将redis连接相关的上下文指针(通过redisConnect模块函数获取到的指针),对应的资源给释放掉。相关C源码也位于module_redis.c文件里:

/**
 * redisFree模块函数,将redis连接相关的上下文指针,对应的资源给释放掉
 * 第一个参数context表示需要释放的redis连接相关的上下文指针
 */
ZL_EXP_VOID module_redis_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: redisFree(context)");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_INT) {
		zenglApi_Exit(VM_ARG,"the first argument [context] of redisFree must be integer");
	}
	redisContext * context = (redisContext *)arg.val.integer;
	MAIN_DATA * my_data = zenglApi_GetExtraData(VM_ARG, "my_data");
	if(!is_valid_redis_context(&(my_data->resource_list), context)) {
		zenglApi_Exit(VM_ARG,"redisFree runtime error: invalid context");
	}
	redisFree(context);
	int ret_code = resource_list_remove_member(&(my_data->resource_list), context);
	if(ret_code != 0) {
		zenglApi_Exit(VM_ARG, "redisFree remove resource from resource_list failed, resource_list_remove_member error code:%d", ret_code);
	}
	zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, ZL_EXP_TRUE, 0);
}

test_redis.zl测试脚本:

    为了测试redis模块,当前版本在my_webroot目录中增加了v0_19_0子目录,并在该子目录中增加了test_redis.zl的测试脚本,该脚本的代码如下:

use builtin, redis;
def TRUE 1;
def FALSE 0;

fun exit(error)
	print error;
	bltExit();
endfun

if(bltIsRunInCmd())
	bltSetImmediatePrint(TRUE);
	print 'now in cmd';
endif

// 连接redis服务器
if(!redisConnect("127.0.0.1", 6379, &con, &error, 30))
	exit(error);
endif

// 向redis服务器发送 get name 命令
if(!redisCommand(con, "get name", &result, &is_null, &error))
	exit(error);
endif

// 判断命令的执行结果是否为空,不为空则将结果打印出来
if(is_null)
	print '*** null ***';
else
	print result;
endif

// 以数组的形式发送命令:hset hash2 testname "say \"hello world!\""
if(!redisCommand(con, bltArray('hset', 'hash2', 'testname', 'say "hello world!"'), &result, &is_null, &error))
	exit(error);
else
	print result;
endif

// 执行命令 hgetall hash2 ,并将结果转为哈希数组
if(!redisCommand(con, "hgetall hash2", &result, &is_null, &error, TRUE))
	exit(error);
endif

if(is_null)
	print '*** null ***';
else
	print result;
	// 循环将哈希数组中每个成员的键名和值打印出来
	for(i=0;bltIterArray(result,&i,&k,&v);)
		print k + ':' + v;
	endfor
endif

redisFree(con);

    要测试该脚本的话,需要先在本地安装并启动redis服务,该脚本的执行结果如下:

[root@localhost zenglServerTest]# ./zenglServer -r "/v0_19_0/test_redis.zl"
now in cmd
redisConnect error: Connection refused
[root@localhost zenglServerTest]# redis-server /etc/redis.conf 
[root@localhost zenglServerTest]# ./zenglServer -r "/v0_19_0/test_redis.zl"
now in cmd
*** null ***
0
[array or class obj type]
testname:say "hello world!"
[root@localhost zenglServerTest]# 

    可以看到,在没有启动redis时,脚本会返回"Connection refused"连接被拒绝的错误。当启动redis后,脚本就可以通过redis模块来设置和获取redis的缓存数据了。

结束语:

    自我控制是最强者的本能。

—— 萧伯纳

 

上下篇

下一篇: zenglServer v0.20.0 增加openssl加密解密相关的模块

上一篇: zenglServer v0.18.0 直接在命令行中执行脚本

相关文章

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

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

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

zenglServer v0.5.0 设置响应头, 获取cookie

zenglServer v0.8.0-v0.8.1

zenglServer v0.14.0 正则表达式模块