建议所有想了解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; };