发布 redis 扩展库

By terrorist at 2021-12-25 • 0人收藏 • 313人看过

https://github.com/btx638/redis-aardio


使用交流可加5000人群 https://kaihei.co/Wmc8bO

7 个回复 | 最后更新于 28 天前
2021-12-25   #1

github又打不开了

大T, 你这个和扩展库里那个hiredis有啥区别?

2021-12-25   #2

多刷几次又打开了......


为了方便git不方便上的人看, 代码展示如下, 切记最新代码还是从github链接那里下载.

库代码如下:

import wsock.tcp.client;

namespace aaz;
class redis{
	ctor(){
		..table.gc(this, "close")
	};
	close = function(){
		if(this.tcpClient){
			this.tcpClient.close();
			this.tcpClient = null;
		}
	};
	connect = function(ip="127.0.0.1", port=6379, timeout=3){
		this.tcpClient := ..wsock.tcp.client();
		if(!this.tcpClient.connectTimeout(ip, port, timeout)){
			return false, "连接服务器失败"; 
		}
		return true;
	};
	setTimeouts = function(send=5000,receive=5000){
		if(this.tcpClient){
			return this.tcpClient.setTimeouts(send, receive); 
		}
	};
	command = function(...){
		var args = {...}; 
		
		var ptr = ..raw.realloc(1);
		// *<参数数量> CR LF
		ptr = ..raw.concat(ptr, resp.arrays ++ #args ++ resp.CRLF); 
		
		// $<参数 i 的字节数量> CR LF
		// <参数 i 的数据> CR LF
		for(i=1;#args;1){
			ptr = ..raw.concat(ptr, 
				resp.bulkStrings ++ #args[i] ++ resp.CRLF ++ 
				args[i] ++ resp.CRLF
			);
		}

		var ok, err = this.tcpClient.write(..raw.tostring(ptr));
		ptr = ..raw.realloc(0, ptr);
		
		if(!ok){
			this.tcpClient.close();
			return false, err; 
		}
		return this.readReply(); 
	};
	readReply = function(){
		// 无参数表示读取一行,包含尾部的 CRLF,但返回的数据不包含尾部的 CRLF 这2个字节
		var line = this.tcpClient.read();
		if(!line){
			 // 获取错误代码, 10060 超时  10054 中断*/
			var errCode = ::Ws2_32.WSAGetLastError();
			// 超时关闭连接
			if(errCode == 10060){
				this.tcpClient.close();
			}
			return false, errCode; 
		}
		
		var prefix = ..string.charCodeAt(line); 
		select(prefix) {
			case 43/*status + Simple Strings*/ {
				return ..string.slice(line, 2); 
			}
			case 45/*error - 当用户对不正确的数据类型执行命令, 或者执行一个不存在的命令。收到错误回复时产生一个异常。*/ { 
				var err = ..string.slice(line, 2)
				error(err, 2)
			}
			case 58/*Integers : 必须能够用 64 位有符号整数表示*/ {
				return tonumber(..string.slice(line, 2)); 
			} 
			case 36/*bulk $ "$6\r\nfoobar\r\n" 批量字符串是用来表示一个单一的二进制安全字符串,长度可达512MB*/{
				var size = tonumber(..string.slice(line, 2));
				// 如果被请求的值不存在, 那么批量回复会将特殊值 -1 用作回复的长度值
				// 客户端应该返回空对象,而不是空字符串
				if(size<0){
					return null; 
				}
				
				var data = this.tcpClient.read(size)
				if(!data){
					var errCode = ::Ws2_32.WSAGetLastError();
					if(errCode == 10060){
						this.tcpClient.close();
					}
					return false, errCode; 	
				}
				
				// 读末尾的 CRLF
				var dummy = this.tcpClient.read(2)
				if(!dummy){
					var errCode = ::Ws2_32.WSAGetLastError();
					if(errCode == 10060){
						this.tcpClient.close();
					}
					return false, errCode; 	
				}
				return data; 
			}
			case 42/*multi bulk * 多条批量回复是由多个回复组成的数组, 数组中的每个元素都可以是任意类型的回复, 包括多条批量回复本身。*/{
				var n = tonumber(..string.slice(line, 2));
				/*
				无内容的多条批量回复(null multi bulk reply)也是存在的, 
				比如当 BLPOP 命令的阻塞时间超过最大时限时, 
				它就返回一个无内容的多条批量回复, 这个回复的计数值为 -1
				
				客户端库应该区别对待空白多条回复和无内容多条回复: 
				当 Redis 返回一个无内容多条回复时, 客户端库应该返回一个 null 对象, 而不是一个空数组。
				*/
				if(n<0){
					return null; 
				}
				var tab = {};
				for(i=1;n;1){
					var res, err = this.readReply();	
					if(res){
						tab[i] = res;
					}
					elseif(res === false){
						return false, err; 
					}
					elseif(res === null){
						tab[i] = null;
					}	
				}
				return tab; 
			}
		}			
	};
	set = function(key, value){
		var ret, err = this.command("set", key, value);
		return ret == "OK", err; 
	};
	//  当 key 不存在
	setnx = function(key, value){
		var ret, err = this.command("SETNX", key, value);
		return ret == 1, err; 
	};
	// 指定过期时间,秒为单位
	setex = function(key, value, seconds){
		var ret, err = this.command("SETEX", key, tostring(seconds), value); 
		return ret == "OK", err; 
	};
	// 指定过期时间,毫秒为单位
	psetex = function(key, value, seconds){
		var ret, err = this.command("PSETEX", key, tostring(seconds), value); 
		return ret == "OK", err; 
	};
	ttl = function(key){
		return this.command("TTL", key); 
	};
	pttl = function(key){
		return this.command("PTTL", key); 
	};
	// 如果 key 中存储的值不是字符串,则会返回错误,因为 GET 仅处理字符串值
	get = function(key, value){
		return this.command("get", key);
	};
	/*
	第一次设置 key 返回 field 数量
	重复设置 key 返回 0 
	*/
	hset = function(key, field, value, ...){
		var ret, err;
		
		if(..table.type(field) == "object"){
			var args = {}
			for(k,v in field){
				..table.push(args, tostring(k), tostring(v))
			}
			ret, err = this.command("HSET", key, ..table.unpack(args)); 
		}
		else {
			ret, err = this.command("HSET", key, field, value, ...); 
			
		}

		if(ret >= 0){
			return true, ret; 
		}
		return false, err;
	};
	hget = function(key, field){
		return this.command("HGET", key, field); 
	}; 
	hgetall = function(key){
		var res, err = this.command("HGETALL", key);
		if(res === false){
			return res, err; 
		}
		
		var ret = {};
		for(i=1;#res;2){
			var k = res[i];
			var v = res[i+1];
			ret[k] = v;
		}
		return ret;
	};
}
namespace redis{
    resp = {
        simpleStrings = "+";
        errors = "-";
        integers = ":";
        bulkStrings = "$";
        arrays = "*";
        CRLF = '\r\n';
    }
    
    respCharCode = {
        simpleStrings = 43;
        errors = 45;
        integers = 58;
        bulkStrings = 36;
        arrays = 42;
    } 	
}
// http://doc.redisfans.com/
// https://github.com/tporadowski/redis
// https://github.com/tporadowski/redis/releases
/**intellisense()
aaz.redis() = 创建 redis 客户端\n!aaz_redis.
aaz.redis = redis 客户端
end intellisense**/

/**intellisense(!aaz_redis)
set(.(key,value) = 将字符串值 value 关联到 key
ttl(.(key) = 以秒为单位,返回给定 key 的剩余生存时间 当 key 不存在时,返回 -2 , \n当 key 存在但没有设置剩余生存时间时,返回 -1
pttl(.(key) = 以毫秒为单位,返回给定 key 的剩余生存时间 当 key 不存在时,返回 -2 , \n当 key 存在但没有设置剩余生存时间时,返回 -1 
end intellisense**/


demo代码如下:

import console; 

import aaz.redis;

var redis = aaz.redis()
redis.connect() 

console.log( redis.setex("key1", "value", "2") )
console.log(redis.get("key1"))

console.pause(true);


2021-12-27   #3

区别是用 wsock.tcp.client 根据协议实现一次。

2021-12-27   #4

2021-12-28   #5

不依赖第三方库,自己实现 redis 协议还是很不错的。
aardio 里很多库都是基于这个原则去实现,功能可以少一些,但尽可能的干净、绿色、轻量,这会带来非常多的好处。

我简单看了一下源码,command 函数代码里仍然使用了很多字符串拼接,raw.realloc 带来的优化效果有限 —— 可以不用,其次,tcpClient.write() 其实非常适合用来连续输入各种复杂的字符串、甚至是结构体,一般没必要再去做一次拼接,下面是修改后的代码供参考:

command = function(...){
    var args = {...}; 
    
    this.tcpClient.write("*",#args,'\r\n');
    for(i=1;#args;1){
        if( ! this.tcpClient.write("$",#args[i],'\r\n',args[i],'\r\n') ) {
            return false,..wsock.err.lasterr(); 
        }
    } 
  
    return this.readReply(); 
};

 redis.resp.bulkStrings 这些其实可以写成  “$”,这个协议的格式字符是固定不变的。

另外,对于同步套接字,read,write 失败可以认为是断开连接了,用户其实不关心太细的错误信息,一般没有必要去过多地 WSAGetLastError 或者每次都去 tcpClient.close 一下,你只要在重连以前检测一下,如果已经连接了先 tcpClient.close 就行了,如果是为了调试, 用 wsock.err.lasterr() 拿错误信息就可以了。

异步套接字麻烦一些,不过 aardio 里的异步套接字也进行了封装,错误处理流程也相对简单。

string.charCodeAt 其实是用来计算宽字符的 Unicode 编码的,你这里只是想取字节码,直接 line[1] 更方便一些。这种代码我一般会先 this.tcpClient.read(1) 读一个字节,修改后的 readReply 函数:

readReply = function(){
    var prefix = this.tcpClient.read(1);  
    if(!prefix) return false,..wsock.err.lasterr(); 

    var data = this.tcpClient.read();
    select(prefix) {
        case '+' {
            return data; 
        }
        case ':'{ 
            return tonumber( data ); 
        } 
        case '$'{
            var size = tonumber( data );
            if(size<0)  return false,..wsock.err.lasterr();  
             
            var data = this.tcpClient.read(size)
            if(!data){ return;  }
             
            this.tcpClient.read(2)
            return data; 
        }
        case '*'{ 
            var n = tonumber(data);
            if(n<0){  return null;}
            
            var tab = {};
            for(i=1;n;1){
                var res, err = this.readReply();    
                if(res)  tab[i] = res; 
                elseif(res === false) return false, err;  
                elseif(res === null)   tab[i] = null;
            }
            return tab; 
        }
        case '-' { 
            error( data, 2)
        }
    }           
};


代码没有仔细测试,也没有仔细看 redis 协议。以上仅供参考。


2021-12-28   #6

回复#5 @jacen :

感谢改进

28 天前   #7

打不开 GitHub ,可以运行下面的代码安装:

import web.rest.github;
web.rest.github.import("https://github.com/btx638/redis-aardio/blob/main/aaz/redis/_.aardio");


登录后方可回帖

登 录
信息栏
本站永久域名:HtmLayout.Cn
aardio可以快速开发上位机,本站主要记录了学习过程中遇到的问题和解决办法及aardio代码分享

这里主要专注于aardio学习交流和经验分享.
纯私人站,当笔记本用的,学到哪写到哪.

AARDIO语言QQ群:70517368
Aardio 官方站:Aardio官方
Aardio最新功能:Aardio官方更新日志
C大Aardio论坛:Aar爱好者论坛
本 站 主 站:Stm32cube中文网
Sciter中文在线文档Sciter在线学习文档
赞助商:才仁机械
Loading...