websocket协议的服务端nodejs实现 - CNode技术社区

websocket协议的服务端nodejs实现
发布于 13 年前 作者 straybird 10276 次浏览 最后一次编辑是 9 年前

虽然已经有很完善的websocket框架,但是从最底层的socket层面实现一下websocket还是很有意思的。 要处理的问题包括:

  1. 握手协议
  2. 数据帧头处理
  3. 数据实际长度和长数据
  4. 编码问题

精华都在下面这张图:

enter image description here

var net = require('net');
var crypto = require('crypto');
var events = require("events");
exports.createServer = function (onConnect) {
var server = net.createServer(function (con) {
 var client = new WsClient(con);
 onConnect(client);
});
this.listen = function(port, onlisten){
 server.listen(port, onlisten);
}
return this;
}
function WsClient(con) {
var _t = this;
var registered = false;
var dataLength = 0;
var recevBuf = null;
var mask = null;
var recevHead = null;
var eve = new events.EventEmitter();
this.on = function (_event, _listenter) {
 eve.on(_event, _listenter);
 return this;
};
con.on('data', function (data) {
 if (!registered) {
 _shakeHand(data, con);
 registered = true;
 eve.emit('connect', con)
 } else {
 _readData(data);
 }
});
con.on('end', function () {
 eve.emit('close');
});
function _shakeHand(data) {
 var data = data.toString().split('\r\n');
 var header = {};
 for (var i = 0; i < data.length; i++) {
 var index = data[i].indexOf(':');
 if (index > 0) {
 var key = data[i].substr(0, index);
 var value = data[i].substr(index + 1);
 header[key.trim()] = value.trim();
 }
 }
 var shasum = crypto.createHash('sha1');
 var m_Magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
 shasum.update(header['Sec-WebSocket-Key'] + m_Magic, 'ascii');
 var respond = 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n';
 respond += 'Upgrade: ' + header['Upgrade'] + '\r\n';
 respond += 'Connection: ' + header['Connection'] + '\r\n';
 respond += 'Sec-WebSocket-Accept: ' + shasum.digest('base64') + '\r\n';
 respond += 'WebSocket-Origin: ' + header['Origin'] + '\r\n';
 respond += 'WebSocket-Location: ' + header['Host'] + '\r\n';
 respond += '\r\n';
 con.write(respond, 'ascii');
}
function _readFrameHead(data) {
 if (dataLength == 0) {
 recevHead = data[0];
 //获取实际数据Payload长度
 var length = data[1] & 0x7F;
 //是否使用掩码
 var hasMarsk = (data[1] & 0x80) == 0x80;
 var marskIndex = 2;
 var dataIndex = 6;
 if (length == 126) {
 marskIndex = 4;
 dataIndex = 8;
 length = data.readUInt16BE(2);
 } else if (length == 127) {
 marskIndex = 10;
 dataIndex = 14;
 length = data.readUInt32BE(6);
 }
 dataLength = length;
 if (hasMarsk) {
 recevBuf = new Buffer(length);
 mask = data.slice(marskIndex, marskIndex + 4);
 } else {
 recevBuf = '';
 mask = null;
 }
 return dataIndex;
 } else {
 return 0;
 }
}
function _readData(data) {
 var dataIndex = _readFrameHead(data);
 if (mask) {
 var i = recevBuf.length - dataLength;
 var l = Math.min(recevBuf.length, data.length - dataIndex + i);
 dataLength -= data.length - dataIndex;
 for (; i < l; i++) {
 recevBuf[i] = data[dataIndex++] ^ mask[i % 4];
 }
 } else {
 recevBuf += data.toString('utf8', dataIndex);
 dataLength -= data.length - dataIndex;
 }
 if (dataLength == 0) {
 if (recevBuf.length > 0) {
 var out = recevBuf.toString();
 eve.emit('data', out);
 } else {
 eve.emit('close');
 }
 }
}
this.send = function (message) {
 var length = Buffer.byteLength(message);
 var dataIndex = length > 65535 ? 10 : length > 125 ? 4 : 2;
 var buf = new Buffer(dataIndex + length);
 buf[0] = recevHead;
 if (length > 65535) {
 buf[1] = 127;
 buf.writeUInt32BE(0, 2);
 buf.writeUInt32BE(length, 6);
 } else if (length > 125) {
 buf[1] = 126;
 buf.writeUInt16BE(length, 2);
 } else {
 buf.writeUInt8(length, 1);
 }
 buf.write(message, dataIndex);
 con.write(buf, 'utf8', function () {
 });
}
}
OpCode = {
Text: 1,
Binary: 2,
Close: 8,
Ping: 9,
Pong: 10
}
2 回复

如果是show代码 问题相当严重

你根本没管理data的长度 原始socket发出的data事件 data长度是不确定的

写服务端代码 你无法信任你的客户端发送什么 发送多少 什么时候发送

只是拿协议练下手,实际应用中还有很多安全问题要考虑,工作项目中当然直接用socket.io

回到顶部

AltStyle によって変換されたページ (->オリジナル) /