node.js 使用 net 模塊模擬 websocket 握手進(jìn)行數(shù)據(jù)傳遞操作示例
本文實(shí)例講述了node.js 使用 net 模塊模擬 websocket 握手進(jìn)行數(shù)據(jù)傳遞操作。分享給大家供大家參考,具體如下:
websocket 是一種讓瀏覽器與服務(wù)器之間建立持久的連接,并能進(jìn)行雙向數(shù)據(jù)傳輸?shù)囊环N協(xié)議。
websocket 屬性應(yīng)用層協(xié)議,基于tcp傳輸協(xié)議,并復(fù)用http的握手通道。
一、如何進(jìn)行websocket連接。
websocket復(fù)用了http的握手通道,客戶(hù)端通過(guò)http請(qǐng)求與服務(wù)端進(jìn)行協(xié)商,升級(jí)協(xié)議。協(xié)議升級(jí)完后,后面的數(shù)據(jù)交換則遵照websocket協(xié)議。
1、客戶(hù)端申請(qǐng)協(xié)議升級(jí)
Request URL: ws://localhost:8888/ Request Method: GET Connection: Upgrade Upgrade: websocket Sec-WebSocket-Version: 13 Sec-WebSocket-Key: uR5YP/BMO6M24tAFcmHeXw== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
- Connection: Upgrade 表示要升級(jí)協(xié)議
- Upgrade: websocket 表示升級(jí)到websocket協(xié)議
- Sec-WebSocket-Version: 13 表示websocket的版本
- Sec-WebSocket-Key 表示websocket的驗(yàn)證,防止惡意的連接,與服務(wù)端響應(yīng)的Sec-WebSocket-Accept是配套。
2、服務(wù)端響應(yīng)協(xié)議升級(jí)
Status Code: 101 Switching Protocols Connection: Upgrade Sec-WebSocket-Accept: eS92kXpBNI6fWsCkj6WxH6QeoHs= Upgrade: websocket
Status Code:101 表示狀態(tài)碼,協(xié)議切換。
Sec-WebSocket-Accept 表示服務(wù)端響應(yīng)的校驗(yàn),與客戶(hù)端的Sec-WebSocket-Key是配套的。
3、Sec-WebSocket-Accept是如何計(jì)算的
將 Sec-WebSocket-Key 的值與 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接。
然后通過(guò)sha1計(jì)算,再轉(zhuǎn)成base64。
const crypto = require('crypto');
function getSecWebSocketAccept(key) {
return crypto.createHash('sha1')
.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
.digest('base64');
}
console.log(getSecWebSocketAccept('uR5YP/BMO6M24tAFcmHeXw=='));
4、協(xié)議升級(jí)完后,后續(xù)的數(shù)據(jù)傳輸就需要按websocket協(xié)議來(lái)走。
websocket客戶(hù)端與服務(wù)端通信的最小單位是 幀,由1個(gè)或多個(gè)幀組成完整的消息。
客戶(hù)端:將消息切割成多個(gè)幀,發(fā)送給服務(wù)端。
服務(wù)端:接收到消息幀,將幀重新組裝成完整的消息。
5、數(shù)據(jù)幀的格式
單位是1個(gè)比特位,F(xiàn)IN,PSV1,PSV2,PSV3 占1個(gè)比特位,opcode占4個(gè)比特位。
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+-------------------------------+ | Extended payload length continued, if payload len == 127 | +-------------------------------+-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------+-------------------------------+ | Payload Data continued ... | +---------------------------------------------------------------+ | Payload Data continued ... | +---------------------------------------------------------------+
FIN 占1位,用來(lái)表示該幀是否是最后一幀,1表示是,0表示不是。
RSV1,RSV2,RSV3 分別占1位,一般情況下全為0,擴(kuò)展使用,值的含義由擴(kuò)展進(jìn)行定義。
opcode 占4位,表示如何解析后面的數(shù)據(jù)載荷(Payload Data)。
%x0 表示一個(gè)延續(xù)幀,opcode為0時(shí),表示數(shù)據(jù)傳輸采用了數(shù)據(jù)分片,當(dāng)前的數(shù)據(jù)幀只是其中一個(gè)數(shù)據(jù)分片。
%x1 表示這是一個(gè)文本幀
%x2 表示這是一個(gè)二進(jìn)制幀
%x3-7 保留的操作代碼,用于定義后續(xù)的非控制幀。
%x8 表示連接斷開(kāi)
%x9 表示這是一個(gè)ping操作
%xA 表示這是一個(gè)pong操作
%xB-F 保留的操作代碼,用于定義后續(xù)的控制幀。
MASK 占1位,表示是否要對(duì)數(shù)據(jù)載荷進(jìn)行掩碼操作。
客戶(hù)端向服務(wù)端發(fā)數(shù)據(jù),需要對(duì)數(shù)據(jù)進(jìn)行掩碼操作,服務(wù)端向客戶(hù)端發(fā)數(shù)據(jù),不需要對(duì)數(shù)據(jù)進(jìn)行掩碼操作。
如果Mask為1,則Masking-key中會(huì)定義一個(gè)掩碼鍵,通過(guò)該掩碼鍵對(duì)數(shù)據(jù)載荷進(jìn)行反掩碼??蛻?hù)端發(fā)送給服務(wù)端的數(shù)據(jù)幀,MASK都是1。
Payload len 為7位,或7+16位,或7+64位,表示數(shù)據(jù)載荷的長(zhǎng)度,單位字節(jié)。
如果Payload len=0~125,表示,數(shù)據(jù)的長(zhǎng)度為0~125字節(jié)。
如果Payload len=126,表示,后續(xù)的2個(gè)字節(jié)代表一個(gè)16位的無(wú)符號(hào)整數(shù),該整數(shù)表示數(shù)據(jù)的長(zhǎng)度。
如果Payload len=127,表示,后續(xù)的8個(gè)字節(jié)代表一個(gè)64位的無(wú)符號(hào)整數(shù),該整數(shù)表示數(shù)據(jù)的長(zhǎng)度。
如果Payload len占用多個(gè)字節(jié),Payload len的二進(jìn)制表達(dá)采用Big-endian。
Masking-key 占0或32位,客戶(hù)端向服務(wù)端發(fā)送數(shù)據(jù)幀,數(shù)據(jù)載荷都進(jìn)行了掩碼操作,Mask為1,且?guī)Я?字節(jié)的Masking-key。如果Mask為0,則沒(méi)有Masking-key。
注意數(shù)據(jù)載荷的長(zhǎng)度,不包括Masking-key的長(zhǎng)度。
6、掩碼的算法
Masking-key掩碼鍵是由客戶(hù)端生成的32位隨機(jī)數(shù),掩碼操作不會(huì)影響數(shù)據(jù)載荷的長(zhǎng)度。
function unmask(buffer, mask) {
const length = buffer.length;
for (var i = 0; i < length; i++) {
buffer[i] ^= mask[i & 3];
}
}
7、實(shí)現(xiàn)websocket的握手
const crypto = require('crypto');
const net = require('net');
//計(jì)算websocket校驗(yàn)
function getSecWebSocketAccept(key) {
return crypto.createHash('sha1')
.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
.digest('base64');
}
//掩碼操作
function unmask(buffer, mask) {
const length = buffer.length;
for (var i = 0; i < length; i++) {
buffer[i] ^= mask[i & 3];
}
}
//創(chuàng)建一個(gè)tcp服務(wù)器
let server = net.createServer(function (socket) {
socket.once('data', function (data) {
data = data.toString();
//查看請(qǐng)求頭中是否有升級(jí)websocket協(xié)議的頭信息
if (data.match(/Upgrade: websocket/)) {
let rows = data.split('\r\n');
//去掉第一行的請(qǐng)求行
//去掉請(qǐng)求頭的尾部?jī)蓚€(gè)空行
rows = rows.slice(1, -2);
let headers = {};
rows.forEach(function (value) {
let [k, v] = value.split(': ');
headers[k] = v;
});
//判斷websocket的版本
if (headers['Sec-WebSocket-Version'] == 13) {
let secWebSocketKey = headers['Sec-WebSocket-Key'];
//計(jì)算websocket校驗(yàn)
let secWebSocketAccept = getSecWebSocketAccept(secWebSocketKey);
//服務(wù)端響應(yīng)的內(nèi)容
let res = [
'HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
`Sec-WebSocket-Accept: ${secWebSocketAccept}`,
'Connection: Upgrade',
'\r\n'
].join('\r\n');
//給客戶(hù)端發(fā)送響應(yīng)內(nèi)容
socket.write(res);
//注意這里不要斷開(kāi)連接,繼續(xù)監(jiān)聽(tīng)'data'事件
socket.on('data', function (buffer) {
//注意buffer的最小單位是一個(gè)字節(jié)
//取第一個(gè)字節(jié)的第一位,判斷是否是結(jié)束位
let fin = (buffer[0] & 0b10000000) === 0b10000000;
//取第一個(gè)字節(jié)的后四位,得到的一個(gè)是十進(jìn)制數(shù)
let opcode = buffer[0] & 0b00001111;
//取第二個(gè)字節(jié)的第一位是否是1,判斷是否掩碼操作
let mask = buffer[1] & 0b100000000 === 0b100000000;
//載荷數(shù)據(jù)的長(zhǎng)度
let payloadLength = buffer[1] & 0b01111111;
//掩碼鍵,占4個(gè)字節(jié)
let maskingKey = buffer.slice(2, 6);
//載荷數(shù)據(jù),就是客戶(hù)端發(fā)送的實(shí)際數(shù)據(jù)
let payloadData = buffer.slice(6);
//對(duì)數(shù)據(jù)進(jìn)行解碼處理
unmask(payloadData, maskingKey);
//向客戶(hù)端響應(yīng)數(shù)據(jù)
let send = Buffer.alloc(2 + payloadData.length);
//0b10000000表示發(fā)送結(jié)束
send[0] = opcode | 0b10000000;
//載荷數(shù)據(jù)的長(zhǎng)度
send[1] = payloadData.length;
payloadData.copy(send, 2);
socket.write(send);
});
}
}
});
socket.on('error', function (err) {
console.log(err);
});
socket.on('end', function () {
console.log('連接結(jié)束');
});
socket.on('close', function () {
console.log('連接關(guān)閉');
});
});
//監(jiān)聽(tīng)8888端口
server.listen(8888);
index.html的代碼:
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script>
var ws = new WebSocket('ws://localhost:8888');
ws.onopen = function () {
console.log('連接成功');
ws.send('你好服務(wù)端');
};
ws.onmessage = function (ev) {
console.log('接收數(shù)據(jù)', ev.data);
};
ws.onclose = function () {
console.log('連接斷開(kāi)');
};
</script>
</body>
</html>
希望本文所述對(duì)大家node.js程序設(shè)計(jì)有所幫助。
相關(guān)文章
手把手教你更優(yōu)雅的修改node_modules里的代碼
這篇文章主要給大家介紹了關(guān)于如何更優(yōu)雅的修改node_modules里的代碼的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-02-02
Node.js視頻流應(yīng)用創(chuàng)建之后端的全過(guò)程
這篇文章主要給大家介紹了關(guān)于創(chuàng)建Node.js視頻流應(yīng)用之后端的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-03-03
Node.js中Request模塊處理HTTP協(xié)議請(qǐng)求的基本使用教程
這篇文章主要介紹了Node.js中Request模塊處理HTTP請(qǐng)求的基本使用教程,request也支持OAuth的簽名請(qǐng)求,很好很強(qiáng)大,需要的朋友可以參考下2016-03-03
使用 Node.js 對(duì)文本內(nèi)容分詞和關(guān)鍵詞抽取
這篇文章主要介紹了使用 Node.js 對(duì)文本內(nèi)容分詞和關(guān)鍵詞抽取,需要的朋友可以參考下2017-05-05
Node.js實(shí)現(xiàn)分片上傳斷點(diǎn)續(xù)傳示例詳解
這篇文章主要為大家介紹了Node.js實(shí)現(xiàn)分片上傳斷點(diǎn)續(xù)傳示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
iPhone手機(jī)上搭建nodejs服務(wù)器步驟方法
這篇文章主要介紹了iPhone手機(jī)上搭建nodejs服務(wù)器步驟方法,本文給出了詳細(xì)的操作步驟以及操作命令,需要的朋友可以參考下2015-07-07
Node.js?中?fs?模塊的高級(jí)用法實(shí)例詳解
文章介紹了Node.js中fs模塊的高級(jí)用法,包括流式讀寫(xiě)、文件監(jiān)視、PromiseAPI、遞歸目錄操作等,這些功能有助于更好地處理復(fù)雜文件操作場(chǎng)景,提升代碼性能和可維護(hù)性,感興趣的朋友跟隨小編一起看看吧2025-02-02
node.js?實(shí)現(xiàn)手機(jī)號(hào)驗(yàn)證碼登錄功能
這篇文章主要介紹了node.js?實(shí)現(xiàn)手機(jī)號(hào)驗(yàn)證碼登錄功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08

