最近用nodejs做項目,大規模的使用了redis,在windows下開發老是遇到各種問題,因爲redis的協議解析器有問題,本來redis模塊有兩種協議解析的方式,一種是使用js實現的,另一種則是使用redis官方提供的hiredis的模塊,當使用hiredis模塊的時候當然一切正常,但是如果你是windows上的開發者,那麼你沒得選擇,只能使用js版解析器,因爲hiredis的模塊在windows下安裝不了,可是使用js版的解析器的時候就會出現各種問題,曾經改過幾次這個js文件,但是還是沒能把BUG完全修復完,於是一股腦直接重寫了這個解析器,這次改動首先是修復各種協議上的問題,其中最大的區別是在效率的很大程度的提升,爲什麼這麼說?因爲原始的解析器方式是這樣,如果一條完整的數據包有10K,此時因爲網絡原因只傳輸了8K,那麼他必須先嚐試解析,一旦發現包長度不夠,就會等下次2K的數據來了之後再重新解析,意思就是說,之前解析的那8K的時間浪費了。而我改動的則是可以續解析,上次解析到哪裏,下次收到數據包後,再接着上次解析的地方繼續往下執行,節約了很多時間。簡單介紹之後貼上代碼吧,希望能對大家有幫助。
var events = require("events"),
util = require('../util');
exports.name = "fast javascript parser";
exports.debug_mode = false;
function Multi(n)
{
this.total = n;
this.offset = 0;
this.data = [];
}
Multi.prototype.push = function(d)
{
this.offset++;
this.data.push(d);
};
Multi.prototype.isFull = function()
{
return (this.total == this.offset);
};
Multi.prototype.incr = function()
{
this.offset++;
};
function ReplyParser(options)
{
this.name = exports.name;
this.options = options || { };
this._buffer = null;
this._start = -1;
this._offset = 0;
this._encoding = "utf-8";
this._debugMode = options.debug_mode;
this._multi = false;//多條數據?
this._multiData = null;//多條數據成員
this._multiCursor = null;//遊標
this._data = null;//單條數據內容
this._dataType = 0;//單條數據類型
this._dataTotal = 0;//單條數據總大小
this._dataOffset = 0;//單條已接收的數據大小
this._parserFunc = {
'+' : this._parseStatus,
'-' : this._parseError,
':' : this._parseInteger,
'$' : this._parseBulk,
'*' : this._parseMulti
};
}
util.inherits(ReplyParser, events.EventEmitter);
exports.Parser = ReplyParser;
ReplyParser.prototype.execute = function(buf)
{
this.append(buf);
while(this._offset < this._buffer.length)
{
if(!this._dataType){
if(this._buffer[this._offset] == 0x0d ||
this._buffer[this._offset] == 0x0a)
{//上次可能預留下來的結束標記
this._offset++;
continue;
}
this._dataType = this._buffer[this._offset];
this._offset++;
}
var type = String.fromCharCode(this._dataType);
if(this._parserFunc[type]){
var result = this._parserFunc[type].apply(this);
if(result){
if(this._multi){//多行數據
if(type == '*'){//當前處理的是頭信息
if(this._multiCursor.total == -1){//多行數據異常
this._multiCursor.offset = -1;
this._multiCursor.data = null;
}
}else{//多行數據又處理一條
this._multiCursor.push(this._data);
}
//是否需要移動遊標?
if(this._multiCursor.isFull() &&
(this._multiCursor != this._multiData))
{
this._multiData.push(this._multiCursor.data);
this._multiCursor = this._multiData;
}
//完整的多行數據處理完畢
if(this._multiData.isFull()){
this._sendReply(this._multiData.data);
this._reset2();
this._reset3();
}
}else if(type == '-'){//錯誤
this._sendError(this._data);
this._reset3();
}else{//單條數據
this._sendReply(this._data);
this._reset3();
}
//重置數據
this._reset();
}
}else{//非法的定義
break;
}
}
};
ReplyParser.prototype.append = function(buf)
{
//empty
if(!buf){
return ;
}
//first
if(this._buffer === null){
this._buffer = buf;
return ;
}
//append
this._buffer = Buffer.concat([this._buffer.slice(0), buf]);
};
//解析出錯
ReplyParser.prototype._parserError = function (message)
{
this.emit("error", message);
};
//發送錯誤
ReplyParser.prototype._sendError = function (reply)
{
this.emit("reply error", reply);
};
//發送數據
ReplyParser.prototype._sendReply = function (reply)
{
this.emit("reply", reply);
};
//重置數據
ReplyParser.prototype._reset = function()
{
this._start = -1;
this._data = null;
this._dataTotal = 0;
this._dataOffset = 0;
this._dataType = 0;
};
//重置multi數據
ReplyParser.prototype._reset2 = function()
{
this._multi = false;
this._multiData = null;
this._multiCusror = null;
};
//重置buffer
ReplyParser.prototype._reset3 = function()
{
this._buffer = this._buffer.slice(this._offset);
this._offset = 0;
};
//只查找\r,忽略所後面的\n
ReplyParser.prototype._findEnd = function()
{
var offset = this._offset;
while(offset < this._buffer.length)
{
if(this._buffer[offset] == 0x0d){
return offset;
}
offset++;
}
return false;
};
//解析狀態
ReplyParser.prototype._parseStatus = function()
{
if(this._start === -1){
this._start = this._offset;
}
//查找結束標記
var end = this._findEnd();
if(end === false){
this._offset = this._buffer.length;
return false;
}
this._offset = end + 1;
this._data = this._buffer.toString(this._encoding, this._start, end);
return true;
};
//解析錯誤
ReplyParser.prototype._parseError = function()
{
if(this._start === -1){
this._start = this._offset;
}
var end = this._findEnd();
if(end === false){
this._offset = this._buffer.length;
return false;
}
this._offset = end + 1;
this._data = this._buffer.toString(this._encoding, this._start, end);
return true;
};
//解析數字
ReplyParser.prototype._parseInteger = function()
{
if(this._start === -1){
this._start = this._offset;
}
var end = this._findEnd();
if(end === false){
this._offset = this._buffer.length;
return false;
}
this._offset = end + 1;
this._data = +(this._buffer.toString(this._encoding, this._start, end));
return true;
};
//解析單條數據
ReplyParser.prototype._parseBulk = function()
{
//讀頭信息
if(!this._dataTotal)
{
if(this._start === -1){
this._start = this._offset;
}
var end = this._findEnd();
if(end === false){
this._offset = this._buffer.length;
return false;
}
this._offset = end + 1;
this._dataOffset = 0;
this._dataTotal = +(this._buffer.toString(this._encoding, this._start, end));
//空數據
if(this._dataTotal == -1){
return true;
}
}
//緩衝區已空
if(this._offset >= this._buffer.length){
return false;
}
//準備開始讀數據,\n要去掉
if((this._dataOffset == 0) && (this._buffer[this._offset] == 0x0a)){
this._offset++;
}
//分別計算出buffer中剩餘的數據和本次需要的數據長度
var need = this._dataTotal - this._dataOffset;
var remain = this._buffer.length - this._offset;
//buffer中有足夠的數據
if(remain >= need){
this._offset+= need;
this._data = this._buffer.toString(this._encoding, this._offset - this._dataTotal, this._offset);
return true;
}
//buffer中數據不足
this._offset = this._buffer.length;
this._dataOffset+= remain;
return false;
};
//解析多條數據
ReplyParser.prototype._parseMulti = function()
{
this._multi = true;
if(this._start === -1){
this._start = this._offset;
}
var end = this._findEnd();
if(end === false){
this._offset = this._buffer.length;
return false;
}
var n = +(this._buffer.toString(this._encoding, this._start, end));
this._offset = end + 1;
if(this._multiData === null){
this._multiData = new Multi(n);
this._multiCursor = this._multiData;
}else{
var m = new Multi(n);
this._multiCursor = m;
}
return true;
};