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

网友投稿 349 2023-06-25


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

Websocket协议详解

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

WebSocket关键词

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

WebSocket比传统Http的好处

客户端与服务端只建立一个TCP连接,可以使用更少的连接

WebSocket的服务端可以将数据推送到客户端,如实时将证券信息反馈到客户端(这个很关键),实时天气数据,比http请求响应模式更灵活

更轻量的协议头,减少数据传送量

数据帧格式

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

/**

* 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

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 数据

*/

WebSocketConneDGzdkIction.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', opcode, payload);

break;

case opcodes.PING://发送ping做响应

this._doSend(opcodes.PING, buffer);

break;

case opcodes.PONG: //不做处理

break;

case opcodes.CLOSE://close有很多关闭码

let codeDGzdkI, 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, 0);

buf.writeUInt8(b2, 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, socket, upgradeHead) {

"use strict";

var ws = new WebSocketConnection(req, socket, upgradeHead);

connectionHandler(ws);

});

srv.listen(port, host);

};

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


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:java中将科学计数法转换普通计数法的简单方法
下一篇:Java中终止线程的三种方法
相关文章

 发表评论

暂时没有评论,来抢沙发吧~