微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Websocket协议详解及简单实例代码

Websocket协议详解

关于websocket的协议是用来干嘛的,请参考其他文章

WebSocket关键词

HTML5协议,实时,全双工通信,长连接

WebSocket比传统Http的好处

  1. 客户端与服务端只建立一个TCP连接,可以使用更少的连接
  2. WebSocket的服务端可以将数据推送到客户端,如实时将证券信息反馈到客户端(这个很关键),实时天气数据,比http请求响应模式更灵活
  3. 更轻量的协议头,减少数据传送量

数据帧格式

下图为手工打造的数据帧格式

rush:js;">

/**

  • fin |masked | |
  • srv1 | length | |
  • srv2 | (7bit |mask数据 |payload
  • srv3 | 7+2字节 | 4字节 |真实数据
    opcode | 7+64字节 | |
    (4bit)
    /

作以下说明:

1.前8个bit(一个字节) —fin: 是否数据发送完成,为1发送完成为0发送未完。 —srv1,srv2,srv3:留作后用 —opcode:数据类型操作码,4bit表示,其中 TEXT: 1,text类型的字符串 BINARY: 2,二进制数据,通常用来保存图片 CLOSE: 8,关闭连接的数据帧。 PING: 9,心跳检测。ping PONG: 10,心跳检测。pong

rush:js;"> var events = require('events'); var http = require('http'); var crypto = require('crypto'); var util = require('util');

/**

  • 数据类型操作码 TEXT 字符串
  • BINARY 二进制数据 常用来保存照片
  • PING,PONG 用作心跳检测
  • CLOSE 关闭连接的数据帧 (有很多关闭连接的代码 1001,1009,1007,1002)
    */
    var opcodes = {
    TEXT: 1,BINARY: 2,CLOSE: 8,PING: 9,PONG: 10
    };
    var WebSocketConnection = function (req,socket,upgradeHead) {
    "use strict";
    var self = this;

var key = hashWebSocketKey(req.headers['sec-websocket-key']);
/**

  • 写头
    */
    socket.write('HTTP/1.1 101 Web Socket Protocol Handshake \r\n' +
    "Upgrade:WebSocket\r\n" +
    "Connection : Upgrade\r\n" +
    "sec-websocket-accept: " + key + '\r\n\r\n');

/**

  • 接收数据
    */
    socket.on('data',function (buf) {
    self.buffer = Buffer.concat([self.buffer,buf]);
    while (self._processBuffer()) {
}

});
socket.on('close',function (had_error) {
if (!self.closed) {
self.emit("close",1006);
self.closed = true;
}
});
this.socket = socket;
this.buffer = new Buffer(0);
this.closed = false;

};

//websocket连接继承事件
util.inherits(WebSocketConnection,events.EventEmitter);

/*
发送数据函数

  • */
    WebSocketConnection.prototype.send = function (obj) {
    "use strict";
    var opcode;
    var payload;
    if (Buffer.isBuffer(obj)) {
    opcode = opcodes.BINARY;
    payload = obj;
    } else if (typeof obj) {
    opcode = opcodes.TEXT;
    //创造一个utf8的编码 可以被编码为字符串
    payload = new Buffer(obj,'utf8');
    } else {
    throw new Error('cannot send object.Must be string of Buffer');
    }

this._doSend(opcode,payload);
};
/*
关闭连接函数

  • */
    WebSocketConnection.prototype.close = function (code,reason) {
    "use strict";
    var opcode = opcodes.CLOSE;
    var buffer;
    if (code) {
    buffer = new Buffer(Buffer.byteLength(reason) + 2);
    buffer.writeUInt16BE(code,0);
    buffer.write(reason,2);
    } else {
    buffer = new Buffer(0);
    }
    this._doSend(opcode,buffer);
    this.closed = true;
    };

WebSocketConnection.prototype._processBuffer = function () {
"use strict";
var buf = this.buffer;
if (buf.length < 2) {
return;
}
var idx = 2;
var b1 = buf.readUInt8(0); //读取数据帧的前8bit
var fin = b1 & 0x80; //如果为0x80,则标志传输结束
var opcode = b1 & 0x0f;//截取第一个字节的后四位
var b2 = buf.readUInt8(1);//读取数据帧第二个字节
var mask = b2 & 0x80;//判断是否有掩码,客户端必须要有
var length = b2 | 0x7f;//获取length属性 也是小于126数据长度的数据真实值
if (length > 125) {
if (buf.length < 8) {
return;//如果大于125,而字节数小于8,则显然不合规范要求
}
}
if (length === 126) {//获取的值为126 ,表示后两个字节用于表示数据长度
length = buf.readUInt16BE(2);//读取16bit的值
idx += 2;//+2
} else if (length === 127) {//获取的值为126 ,表示后8个字节用于表示数据长度
var highBits = buf.readUInt32BE(2);//(1/0)1111111
if (highBits != 0) {
this.close(1009,"");//1009关闭代码,说明数据太大
}
length = buf.readUInt32BE(6);//从第六到第十个字节为真实存放的数据长度
idx += 8;
}

if (buf.length < idx + 4 + length) {//不够长 4为掩码字节数
return;
}

var maskBytes = buf.slice(idx,idx + 4);//获取掩码数据
idx += 4;//指针前移到真实数据段
var payload = buf.slice(idx,idx + length);
payload = unmask(maskBytes,payload);//解码真实数据
this._handleFrame(opcode,payload);//处理操作码
this.buffer = buf.slice(idx + length);//缓存buffer
return true;
};

/**

  • 针对不同操作码进行不同处理
  • @param 操作码
  • @param 数据
    */
    WebSocketConnection.prototype._handleFrame = function (opcode,buffer) {
    "use strict";
    var payload;
    switch (opcode) {
    case opcodes.TEXT:
    payload = buffer.toString('utf8');//如果是文本需要转化为utf8的编码
    this.emit('data',opcode,payload);//Buffer.toString()默认utf8 这里是故意指示的
    break;
    case opcodes.BINARY: //二进制文件直接交付
    payload = buffer;
    this.emit('data',payload);
    break;
    case opcodes.PING://发送ping做响应
    this._doSend(opcodes.PING,buffer);
    break;
    case opcodes.PONG: //不做处理
    break;
    case opcodes.CLOSE://close有很多关闭码
    let code,reason;//用于获取关闭码和关闭原因
    if (buffer.length >= 2) {
    code = buffer.readUInt16BE(0);
    reason = buffer.toString('utf8',2);
    }
    this.close(code,reason);
    this.emit('close',code,reason);
    break;
    default:
    this.close(1002,'unkNown opcode');
    }
    };

/**

  • 实际发送数据的函数
  • @param opcode 操作码
  • @param payload 数据
  • @private
    */
    WebSocketConnection.prototype._doSend = function (opcode,payload) {
    "use strict";
    this.socket.write(encodeMessage(opcode,payload));//编码后直接通过socket发送
    };

/**

  • 编码数据
  • @param opcode 操作码
  • @param payload 数据
  • @returns {}
    /
    var encodeMessage = function (opcode,payload) {
    "use strict";
    var buf;
    var b1 = 0x80 | opcode;
    var b2;
    var length = payload.length;
    if (length < 126) {
    buf = new Buffer(payload.length + 2 + 0);
    b2 |= length;
    //buffer,offset
    buf.writeUInt8(b1,0);//读前8bit
    buf.writeUInt8(b2,1);//读8―15bit
    //Buffer.prototype.copy = function(targetBuffer,targetStart,sourceStart,sourceEnd) {
    payload.copy(buf,2)//复制数据,从2(第三)字节开始

} else if (length < (1 << 16)) {
buf = new Buffer(payload.length + 2 + 2);
b2 |= 126;
buf.writeUInt8(b1,0);
buf.writeUInt8(b2,1);
buf.writeUInt16BE(length,2)
payload.copy(buf,4);
} else {
buf = new Buffer(payload.length + 2 + 8);
b2 |= 127;
buf.writeUInt8(b1,1);
buf.writeUInt32BE(0,2)
buf.writeUInt32BE(length,6)
payload.copy(buf,10);
}

return buf;
};

/**

  • 解掩码
  • @param maskBytes 掩码数据
  • @param data payload
  • @returns {Buffer}
    */
    var unmask = function (maskBytes,data) {
    var payload = new Buffer(data.length);
    for (var i = 0; i < data.length; i++) {
    payload[i] = maskBytes[i % 4] ^ data[i];
    }
    return payload;
    };
    var KEY_SUFFIX = '258EAFA5-E914-47DA-95CA-C5ABoDC85B11';

/*equals to crypto.createHash('sha1').update(key+'KEY_SUFFIX').digest('base64')

  • */
    var hashWebSocketKey = function (key) {
    "use strict";
    var sha1 = crypto.createHash('sha1');
    sha1.update(key + KEY_SUFFIX,'ascii');
    return sha1.digest('base64');
    };

exports.listen = function (port,host,connectionHandler) {
"use strict";
var srv = http.createServer(function (req,res) {
});

srv.on('upgrade',function (req,upgradeHead) {
"use strict";
var ws = new WebSocketConnection(req,upgradeHead);
connectionHandler(ws);
});
srv.listen(port,host);
};

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持

原文地址:https://www.jb51.cc/js/43647.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐