这里有新鲜出炉的 Javascript 教程,程序狗速度看过来!
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
这篇文章主要介绍了 websocket 协议详解及简单实例代码的相关资料, 这里对 websocket 协议进行详细介绍,并附简单实例代码,需要的朋友可以参考下
Websocket 协议详解
关于 websocket 的协议是用来干嘛的,请参考其他文章。
WebSocket 关键词
html5 协议,实时,全双工通信,长连接
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 数据
- */
- 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', opcode, 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, 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);
- };
来源: http://www.phperz.com/article/17/0531/329658.html