前言:
今天一天在折腾用LUA脚本一次性返回车队集合(set)中的司机位置(hash),因为跨类型获取,新建多个redis实例循环可能更加耗费资源于是便想到用lua脚本来操作一次性返回所有内容,“all in the memory”,这样在redis的lua脚本解析器中执行能减去通信的消耗,lua不会写起来费劲,最后勉强采用神一样的拼接返回了。这个脚本语言真的是很灵活强大,如果未来专门折腾redis的话我愿意深入了解它。
闲言少叙,老雷在发自己新博文的时候我偶然看到了用redis写一个简单redis客户端这篇文章,一看通讯方式貌似很简单,Geemo那个狗逼曾经给我形容的非常非常难,我很久前问他我可以尝试写一下么,难么。狗逼是这么跟我说的:“老雷出的题你说呢,呵呵…”。狗日的,结果今天——
【传说】群主智障是狗
http://morning.work/page/2016-05/how-to-write-a-nodejs-redis-client.html 抓到了。今晚撸这个
【传说】geemo我的 2016/8/3 21:06:10草21:06:23
【传说】geemo我的 2016/8/3 21:06:23群主 我早撸过了
【传说】群主智障是狗 2016/8/3 21:06:41狗逼之前不告诉我怎么通信
【传说】geemo我的 2016/8/3 21:06:43而且我写了个交互式的
【传说】geemo我的 2016/8/3 21:07:21狗逼 你问了我吗
http://morning.work/page/2016-05/how-to-write-a-nodejs-redis-client.html 抓到了。今晚撸这个
【传说】geemo我的 2016/8/3 21:06:10草21:06:23
【传说】geemo我的 2016/8/3 21:06:23群主 我早撸过了
【传说】群主智障是狗 2016/8/3 21:06:41狗逼之前不告诉我怎么通信
【传说】geemo我的 2016/8/3 21:06:43而且我写了个交互式的
【传说】geemo我的 2016/8/3 21:07:21狗逼 你问了我吗
妈的渣渣,自己撸了,然后不告诉我,让我自己体会。(PS:最上面是老雷原文链接)这种人,怎一狗逼了得。
正文:
为了避免无耻照抄的行为,我还是按照老司机的步伐走一遍,总结下。
假如我要执行命令KEYS *,只要往服务器发送KEYS *\r\n即可,这时服务器会直接响应结果,返回的结果格式如下:
用单行回复,回复的第一个字节将是+
错误消息,回复的第一个字节将是-
整型数字,回复的第一个字节将是:
批量回复,回复的第一个字节将是$
多个批量回复,回复的第一个字节将是*
在win下用telnet命令链接服务,telnet命令需要在控制面板-》程序,打开或关闭win应用—》 telnet通讯
正常执行命令会出现如下反馈
//1、返回错误
help
-ERR unknown command ‘help’
//2、操作成功
set abc 123456
+OK
//3、得到结果
get abc
$6
123456
//4、得不到结果
get aaa
$-1
//5、得到的结果是整形数字
hlen aaa
:5
//6、数组结果
keys a*
*3
$3
abc
$3
aa1
$1
a
//7、多命令执行 可以看出是依次执行返回的
multi
+OK
get a
+QUEUED
get b
+QUEUED
get c
+QUEUED
exec
*3
$5
hello
$-1
$5
world
help
-ERR unknown command ‘help’
//2、操作成功
set abc 123456
+OK
//3、得到结果
get abc
$6
123456
//4、得不到结果
get aaa
$-1
//5、得到的结果是整形数字
hlen aaa
:5
//6、数组结果
keys a*
*3
$3
abc
$3
aa1
$1
a
//7、多命令执行 可以看出是依次执行返回的
multi
+OK
get a
+QUEUED
get b
+QUEUED
get c
+QUEUED
exec
*3
$5
hello
$-1
$5
world
//结果解析类(手敲非复制)
class RedisProto{
constructor () {
this._lines = []; //第一次已经解析出来的行
this._text=''; //不能构成一行的文本
}
//添加到缓冲区
push(text) {
//结果安装\r\n进行分割
const lines=(this._text+text).split('\r\n');
//若结尾为'\r\n',那么数组最后一个元素一定为一个空字符串下面将其移除
this._text=lines.pop();
//将下一部分跟date事件相连
this._lines=this._lines.concat(lines);
}
//解析下一个结果,若不存在返回null
next() {
const lines =this._lines;
const first=lines[0];
//去掉指定数量的行返回结果
const popResult = (lineNumber,result)=>{
this._lines = this._lines.slice(lineNumber);
return this.result = result;
}
//返回空结果
const popEmpty = () => {
return this.result=false;
}
if(lines.length<1)
return popEmpty();
switch(first[0]){
case '+':
return popResult(1,{data:first.slice(1)});
case '-':
return popResult(1,{data:first.slice(1)});
case ':':
return popResult(1,{data:Number(first.slice(1))});
case '$':
const n = Number(first.slice(1));
if(n=== -1){
//若为-1,无结果
return popResult(1, {data: null});
}else{
//选取下一行作为结果 判断是否为最后一行
if(typeof second !== 'undefined'){
return popResult(2, {data: second});
}else{
return popEmpty();
}
}
case '*':{
const n = Number(first.slice(1));
if(n===0){
return popResult(1, {data: []});
} else {
const array = [];
let i = 1;
for(; i < lines.length && array.length < n; i++) {
const a = lines[i];
const b = lines[i+1];
if (a.slice(0,3) === '$-1') {
array.push(null);
} else if (a[0] === ':') {
array.push(Number(a.slice(1)));
} else {
if (typeof b !== 'undefined') {
array.push(b);
i++;
} else {
return popEmpty();
}
}
}
if (array.length === n) {
return popResult(i, {data: array});
} else {
return popEmpty();
}
}
}
default:
return popEmpty();
}
}
}
//导出
module.exports = RedisProto;
const proto = new RedisProto();
// 接受到数据
proto.push('*3\r\n$3\r\nabc\r\n$3\r\naa1\r\n$1\r\na\r\n');
proto.push('+OK\r\n');
while (proto.next()) {
// proto.next() 如果有解析出完整的结果则返回结果,没有则返回 false
// 可通过 proto.result 获得
console.log(proto.result);
}
用NET模块实现nodejs与服务器之间的通讯。
//实现数据块链接 实体类
const events = require('events');
const net = require('net');
const RedisProto = require('./test2');
class Redis extends events.EventEmitter{
constructor(options){
super();
// 默认连接配置
options = options || {};
options.host = options.host || '127.0.0.1';
options.port = options.port || 6379;
this.options = options;
// 连接状态
this._isClosed = false;
this._isConnected = false;
// 回调函数列表
this._callbacks = [];
this._proto = new RedisProto();
this.connection = net.createConnection(options.port, options.host, () => {
this._isConnected = true;
this.emit('connect');
});
//注册事件
this.connection.on('error', (err) => {
this.emit('error', err);
});
this.connection.on('close', () => {
this._isClosed = true;
this.emit('close');
});
this.connection.on('end', () => {
this.emit('end');
});
//触发data事件
this.connection.on('data', (data) => {
this._pushData(data);
});
}
// 发送命令给服务器,具体看老雷的文章,回调函数同时支持callback和promise
sendCommand(cmd, callback) {
return new Promise((resolve, reject) => {
const cb = (err, ret) => {
callback && callback(err, ret);
err ? reject(err) : resolve(ret);
};
// 如果当前连接已断开,直接返回错误
if (this._isClosed) {
return cb(new Error('connection has been closed'));
}
// 将回调函数添加到队列
this._callbacks.push(cb);
// 发送命令
this.connection.write(`${cmd}\r\n`);
});
}
// 接收到数据,循环结果
_pushData(data) {
console.log(data.toString())
this._proto.push(data);
while (this._proto.next()) {
const result = this._proto.result;
const cb = this._callbacks.shift();
if (result.error) {
cb(new Error(result.error));
} else {
cb(null, result.data);
}
}
}
// 关闭连接
end() {
this.connection.destroy();
}
}
const client = new Redis();
client.sendCommand('GET a', (err, ret) => {
console.log('b=%s, err=%s', ret, err);
});
client.sendCommand('GET b', (err, ret) => {
console.log('b=%s, err=%s', ret, err);
});
//打开本地redis试了试能用.....
后记:
这样简单的实现了redis客户端的功能,其实老雷下面提到了一些改进和方法(PS:还没沉下心思看),先发这些,全部手敲认证,看了看第一次编辑已经是一个月前了,天,我都干了什么!!回调队列那里还有点迷糊,明天问问geemo。。。
