Websocket協(xié)議詳解及簡單實例代碼
Websocket協(xié)議詳解
關(guān)于websocket的協(xié)議是用來干嘛的,請參考其他文章。
WebSocket關(guān)鍵詞
HTML5協(xié)議,實時,全雙工通信,長連接
WebSocket比傳統(tǒng)Http的好處
- 客戶端與服務(wù)端只建立一個TCP連接,可以使用更少的連接
- WebSocket的服務(wù)端可以將數(shù)據(jù)推送到客戶端,如實時將證券信息反饋到客戶端(這個很關(guān)鍵),實時天氣數(shù)據(jù),比http請求響應(yīng)模式更靈活
- 更輕量的協(xié)議頭,減少數(shù)據(jù)傳送量
數(shù)據(jù)幀格式
下圖為手工打造的數(shù)據(jù)幀格式
/** * fin |masked | | * srv1 | length | | * srv2 | (7bit |mask數(shù)據(jù) |payload * srv3 | 7+2字節(jié) | 4字節(jié) |真實數(shù)據(jù) opcode | 7+64字節(jié) | | *(4bit) */
作以下說明:
1.前8個bit(一個字節(jié))
—fin: 是否數(shù)據(jù)發(fā)送完成,為1發(fā)送完成為0發(fā)送未完。
—srv1,srv2,srv3:留作后用
—opcode:數(shù)據(jù)類型操作碼,4bit表示,其中
TEXT: 1, text類型的字符串
BINARY: 2,二進制數(shù)據(jù),通常用來保存圖片
CLOSE: 8,關(guān)閉連接的數(shù)據(jù)幀。
PING: 9, 心跳檢測。ping
PONG: 10,心跳檢測。pong
var events = require('events');
var http = require('http');
var crypto = require('crypto');
var util = require('util');
/**
* 數(shù)據(jù)類型操作碼 TEXT 字符串
* BINARY 二進制數(shù)據(jù) 常用來保存照片
* PING,PONG 用作心跳檢測
* CLOSE 關(guān)閉連接的數(shù)據(jù)幀 (有很多關(guān)閉連接的代碼 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');
/**
* 接收數(shù)據(jù)
*/
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);
/*
發(fā)送數(shù)據(jù)函數(shù)
* */
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;
//創(chuàng)造一個utf8的編碼 可以被編碼為字符串
payload = new Buffer(obj, 'utf8');
} else {
throw new Error('cannot send object.Must be string of Buffer');
}
this._doSend(opcode, payload);
};
/*
關(guān)閉連接函數(shù)
* */
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); //讀取數(shù)據(jù)幀的前8bit
var fin = b1 & 0x80; //如果為0x80,則標(biāo)志傳輸結(jié)束
var opcode = b1 & 0x0f;//截取第一個字節(jié)的后四位
var b2 = buf.readUInt8(1);//讀取數(shù)據(jù)幀第二個字節(jié)
var mask = b2 & 0x80;//判斷是否有掩碼,客戶端必須要有
var length = b2 | 0x7f;//獲取length屬性 也是小于126數(shù)據(jù)長度的數(shù)據(jù)真實值
if (length > 125) {
if (buf.length < 8) {
return;//如果大于125,而字節(jié)數(shù)小于8,則顯然不合規(guī)范要求
}
}
if (length === 126) {//獲取的值為126 ,表示后兩個字節(jié)用于表示數(shù)據(jù)長度
length = buf.readUInt16BE(2);//讀取16bit的值
idx += 2;//+2
} else if (length === 127) {//獲取的值為126 ,表示后8個字節(jié)用于表示數(shù)據(jù)長度
var highBits = buf.readUInt32BE(2);//(1/0)1111111
if (highBits != 0) {
this.close(1009, "");//1009關(guān)閉代碼,說明數(shù)據(jù)太大
}
length = buf.readUInt32BE(6);//從第六到第十個字節(jié)為真實存放的數(shù)據(jù)長度
idx += 8;
}
if (buf.length < idx + 4 + length) {//不夠長 4為掩碼字節(jié)數(shù)
return;
}
var maskBytes = buf.slice(idx, idx + 4);//獲取掩碼數(shù)據(jù)
idx += 4;//指針前移到真實數(shù)據(jù)段
var payload = buf.slice(idx, idx + length);
payload = unmask(maskBytes, payload);//解碼真實數(shù)據(jù)
this._handleFrame(opcode, payload);//處理操作碼
this.buffer = buf.slice(idx + length);//緩存buffer
return true;
};
/**
* 針對不同操作碼進行不同處理
* @param 操作碼
* @param 數(shù)據(jù)
*/
WebSocketConnection.prototype._handleFrame = function (opcode, buffer) {
"use strict";
var payload;
switch (opcode) {
case opcodes.TEXT:
payload = buffer.toString('utf8');//如果是文本需要轉(zhuǎn)化為utf8的編碼
this.emit('data', opcode, payload);//Buffer.toString()默認utf8 這里是故意指示的
break;
case opcodes.BINARY: //二進制文件直接交付
payload = buffer;
this.emit('data', opcode, payload);
break;
case opcodes.PING://發(fā)送ping做響應(yīng)
this._doSend(opcodes.PING, buffer);
break;
case opcodes.PONG: //不做處理
break;
case opcodes.CLOSE://close有很多關(guān)閉碼
let code, reason;//用于獲取關(guān)閉碼和關(guān)閉原因
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');
}
};
/**
* 實際發(fā)送數(shù)據(jù)的函數(shù)
* @param opcode 操作碼
* @param payload 數(shù)據(jù)
* @private
*/
WebSocketConnection.prototype._doSend = function (opcode, payload) {
"use strict";
this.socket.write(encodeMessage(opcode, payload));//編碼后直接通過socket發(fā)送
};
/**
* 編碼數(shù)據(jù)
* @param opcode 操作碼
* @param payload 數(shù)據(jù)
* @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)//復(fù)制數(shù)據(jù),從2(第三)字節(jié)開始
} 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 掩碼數(shù)據(jù)
* @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);
};
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
- 基于node實現(xiàn)websocket協(xié)議
- Python實現(xiàn)同時兼容老版和新版Socket協(xié)議的一個簡單WebSocket服務(wù)器
- php使用websocket示例詳解
- Javascript WebSocket使用實例介紹(簡明入門教程)
- Python通過websocket與js客戶端通信示例分析
- Nginx反向代理websocket配置實例
- 讓ie6也支持websocket采用flash封裝實現(xiàn)
- 使用swoole擴展php websocket示例
- Spring和Websocket相結(jié)合實現(xiàn)消息的推送
- php+html5基于websocket實現(xiàn)聊天室的方法
- 淺析nodejs實現(xiàn)Websocket的數(shù)據(jù)接收與發(fā)送
- Android中使用WebSocket實現(xiàn)群聊和消息推送功能(不使用WebView)
相關(guān)文章
關(guān)于不同頁面之間實現(xiàn)參數(shù)傳遞的幾種方式討論
下面小編就為大家?guī)硪黄P(guān)于不同頁面之間實現(xiàn)參數(shù)傳遞的幾種方式討論。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02
JavaScript中的Number數(shù)字類型學(xué)習(xí)筆記
對數(shù)字類型支持得不夠強大是很多人吐槽JavaScript的原因,anyway...這里整理了JavaScript中的Number數(shù)字類型學(xué)習(xí)筆記,適合入門及基礎(chǔ)知識復(fù)習(xí),需要的朋友可以參考下2016-05-05
javascript處理表單示例(javascript提交表單)
這篇文章主要介紹了javascript處理表單示例,處理 各種表單, 以及鏈接,按鈕的通用組件,需要的朋友可以參考下2014-04-04

