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

虽然已经有很完善的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

回到顶部