建议所有想了解websocket的朋友都最好先去看看
websocket协议说明(英文):http://www.rfc-editor.org/rfc/rfc6455.txt
中文翻译:https://github.com/wen866595/open-doc/blob/master/rfc/RFC6455-cn.md
好了,废话少说,上代码
/**
* nodejs内置的加密类,用于处理base64加密解密
*/
var cObj = require('crypto');
/**
* 解析请求头部内容
* @param String header 请求头部内容
* @return Array 内容的关联数组
*/
var headerParser = function(header) {
var arr = header.split("\r\n");
var r = [];
for (var i in arr) {
var tmp = arr[i].split(': ');
r[tmp[0]] = tmp[1];
}
return r;
};
/**
* 生成websocket握手所需的KEY
* 若客户端提供的KEY长度不符合rfc6455标准,则返回FALSE
* @param String sKey 客户端提供的KEY
* @return String 符合websocket规则的握手key
*/
var makeSkey = function(sKey) {
var k = new Buffer(sKey, 'base64');
if (k.length != 16) return false;
sKey += '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
var hashObj = cObj.createHash('sha1');
hashObj.update(sKey);
sKey = hashObj.digest('base64');
return sKey;
};
/**
* websocket 通信类
* @param Socket socket 客户端连接的SOCKET对象
* @param Function dataHandle 接收客户端信息的回调
* @return Object 经过封装的websocket类
*/
exports.create = function(socket, dataHandle) {
var _so = socket;
var _sh = false;
var _cb = dataHandle;
var rObj = {
/**
* 底层socket对象的引用
* @var Socket
*/
sobj : socket,
/**
* 连接状态,握手后为true
* @var Boolean
*/
status : false,
/**
* 客户端地址及端口
* @var String
*/
clientAddress : _so.remoteAddress+':'+_so.remotePort,
/**
* 设置回调函数
* @param Function cbFunc
*/
setCallback : function(cbFunc) {_cb = cbFunc;},
/**
* 发送数据到客户端
* 数据会根据websocket协议规则进行编码
* @param String strData
*/
sendData : function(strData) {
var bData = new Buffer(strData.length);
bData.write(strData);
bData = _encode(bData);
console.log('Now send data to client('+_so.remoteAddress+':'+_so.remotePort+')', strData);
_so.write(bData);
},
/**
* 中断与客户端的连接
*/
endConnect : function() {
console.log('Connection close');
_die();
}
};
/**
* 中断连接
* 关闭底层socket对象
*/
var _die = function() {
rObj.status = false;
_so.end();
};
/**
* 编码函数
* 编码采用一次一个数据帧的方式,暂时不支持数据分片传输
* @param Buffer binData 需要编码的二进制数据
* @return Buffer 编码后的数据
*/
var _encode = function(binData) {
var bArr = [0x81];//bytearray,第一byte,10000000, fin = 1, rsv1 rsv2 rsv3均为0, opcode = 0x01,即数据为文本帧
var b2 = 0;//第二byte,用于表示是否有掩码,及负载数据(payload data)的长度,第一bit为是否有掩码,后7bit表示负载数据长度
var extra = null;//额为用于表示数据长度的byte
if (binData.length < 126) //如果数据长度小于126byte,即7bit以内,则不需要额外的byte来表示数据长度
b2 += binData.length;
else if (binData.length >= 126 && binData.length <= 65535) {
//如果数据大于126byte,小于65535byte,即16bit以内,则在第二byte后额外用2byte表示数据长度
b2 = 126;
extra = new Buffer(2);
extra.writeUInt16BE(binData.length, 0);
} else {
//如果数据大于65535byte, 则在第二byte后额外用4byte表示数据长度(数据长度超过0xffffffffffffffff,则需要下一帧,暂时没有实现这个功能)
b2 = 127;
extra = new Buffer(8);
extra.writeUInt32BE(binData.length & 0xFFFF0000 >> 32, 0);
extra.writeUInt32BE(binData.length & 0xFFFF, 4);
}
bArr.push(b2);
var bObj = new Buffer(bArr);
if (b2 >= 126)
bObj = Buffer.concat([bObj, extra]);
//下面这些编码用于添加掩码,但根据rfc6455 第5.1节规定:A server MUST NOT mask any frames that it sends to the client
//所以服务端发送的数据将不加掩码
//var maskData = new Buffer(4);
//for (var i = 0;i < 4;i++) {
// maskData[i] = parseInt(Math.random()*255 + 1);
//}
//bObj = Buffer.concat([bObj,maskData]);
//var mData = new Buffer(binData.length);
//for (var I = 0;I < binData.length;I++) {
//mData[I] = binData[I] ^ (~maskData[I % 4]);
//}
//return Buffer.concat([bObj, mData]);
return Buffer.concat([bObj, binData]);
};
/**
* 解码函数
* 将客户端的数据进行解码,和编码函数一样,暂时不支持分片传输的数据帧
* @param Buffer Data 原始二进制数据
* @retrun 解密后的数据,如果解密失败,则返回false
*/
var _decode = function(Data) {
if ((Data[0] & 0x80 != 0x80)) {
//判断是否分片,如果分片,则停止操作
console.log('is end');
return false;
}
if ((Data[0] & 0x70) != 0) {
//如果三个rsv bits 有不为0的,则停止操作
console.log('rsv not null');
return false;
}
if ((Data[0] & 0x8) != 0) {
//如果opcode为0x8(一般为浏览器发送中断连接的操作),返回中断连接的信号
console.log('close Connection');
return 'CLOSE';
}
//获取掩码及数据长度,并确定payload data起始位置
var hasMasked = ((Data[1] & 0x80) == 0x80);//判断是否有加掩码,如果有,则对负载数据进行解密
var dataLength = Data[1] & 0x7f;
var mask = new Buffer(4);
var dataStart = null;
//确定数据长度和编码函数中表示长度的方法是一样的
if (dataLength == 126) {
var l = new Buffer(2);
Data.copy(l, 0, 2, 4);
dataLength = l.readUInt16BE(0);
if (hasMasked) {
Data.copy(mask, 0, 4, 8);
dataStart = 8;
} else dataStart = 4;
} else if (dataLength == 127) {
for (var I = 2; I < 10; I++) {
dataLength += Data[I] * Math.Pow(255, I - 2);
}
if (hasMasked) {
dataStart = 14;
Data.copy(mask, 0, 10, 14);
} else dataStart = 10;
} else {
if (hasMasked) {
Data.copy(mask, 0, 2, 6);
dataStart = 6;
} else dataStart = 2;
}
if (Data.length != (dataStart + dataLength)) {
console.log('data length error', Data.length, dataStart, dataLength);//检测数据长度是否与客户端声明的是一致的
return false;
}
var t = new Buffer(dataLength);
var trueData = null;
Data.copy(t, 0, dataStart);
if (hasMasked) {
//如果有加掩码,则进行解密操作
trueData = new Buffer(dataLength);
for (var i = 0; i < dataLength; i++) {
trueData[i] = t[i] ^ mask[i % 4];
}
//trueData = trueData.toString();
} else //trueData = t.toString();
trueData = t;
return trueData;
};
/**
* 底层socket对象的监听器
* 监听的事件为data事件
* 如果有数据发送,则根据实际情况进行处理(可能是握手操作,可能是数据解密,可能是关闭连接)
* @param Event node的事件对象,其中data属性就是客户端传输过来的数据(一般来说都是binary)
*/
var _dataHandle = function(d) {
if (!_sh) {
_shakeHands(d.toString());
return;
}
if (_cb == null) return;
console.log('get data size:'+d.length+' Bytes');
console.log('raw data', d);
var binData = _decode(d);
if (binData === 'CLOSE')
_die();
else
_cb(binData, rObj);//解码后的数据回调给用户自定的回调函数,同时将当前websocket类的实例也包含进去
};
/**
* 握手
* 接收并处理websocket连接请求,根据客户端发送的头部信息,解析并进行基本鉴权。然后返回相应的信息
* @param String header 请求的头部信息
*/
var _shakeHands = function(header) {
var h = headerParser(header);
var skey = makeSkey(h['Sec-WebSocket-Key']);
if (skey === false) return _die();
var hs = [
'HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
'Connection: Upgrade',
'Sec-WebSocket-Accept:'+skey,
'',''
];
_so.write(hs.join("\r\n"));
console.log('shake hands complete, client address:', _so.remoteAddress+':'+_so.remotePort);
_sh = true;
rObj.status = true;
};
_so.on('data', _dataHandle);
return rObj;
};