本文實例講述了node.js 使用 net 模塊模擬 websocket 握手進行數(shù)據(jù)傳遞操作。分享給大家供大家參考,具體如下:
創(chuàng)新互聯(lián)建站堅持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時代的東興網(wǎng)站設(shè)計、移動媒體設(shè)計的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
websocket 是一種讓瀏覽器與服務(wù)器之間建立持久的連接,并能進行雙向數(shù)據(jù)傳輸?shù)囊环N協(xié)議。
websocket 屬性應(yīng)用層協(xié)議,基于tcp傳輸協(xié)議,并復(fù)用http的握手通道。
一、如何進行websocket連接。
websocket復(fù)用了http的握手通道,客戶端通過http請求與服務(wù)端進行協(xié)商,升級協(xié)議。協(xié)議升級完后,后面的數(shù)據(jù)交換則遵照websocket協(xié)議。
1、客戶端申請協(xié)議升級
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
2、服務(wù)端響應(yīng)協(xié)議升級
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)的校驗,與客戶端的Sec-WebSocket-Key是配套的。
3、Sec-WebSocket-Accept是如何計算的
將 Sec-WebSocket-Key 的值與 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接。
然后通過sha1計算,再轉(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é)議升級完后,后續(xù)的數(shù)據(jù)傳輸就需要按websocket協(xié)議來走。
websocket客戶端與服務(wù)端通信的最小單位是 幀,由1個或多個幀組成完整的消息。
客戶端:將消息切割成多個幀,發(fā)送給服務(wù)端。
服務(wù)端:接收到消息幀,將幀重新組裝成完整的消息。
5、數(shù)據(jù)幀的格式
單位是1個比特位,F(xiàn)IN,PSV1,PSV2,PSV3 占1個比特位,opcode占4個比特位。
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位,用來表示該幀是否是最后一幀,1表示是,0表示不是。
RSV1,RSV2,RSV3 分別占1位,一般情況下全為0,擴展使用,值的含義由擴展進行定義。
opcode 占4位,表示如何解析后面的數(shù)據(jù)載荷(Payload Data)。
%x0 表示一個延續(xù)幀,opcode為0時,表示數(shù)據(jù)傳輸采用了數(shù)據(jù)分片,當(dāng)前的數(shù)據(jù)幀只是其中一個數(shù)據(jù)分片。
%x1 表示這是一個文本幀
%x2 表示這是一個二進制幀
%x3-7 保留的操作代碼,用于定義后續(xù)的非控制幀。
%x8 表示連接斷開
%x9 表示這是一個ping操作
%xA 表示這是一個pong操作
%xB-F 保留的操作代碼,用于定義后續(xù)的控制幀。
MASK 占1位,表示是否要對數(shù)據(jù)載荷進行掩碼操作。
客戶端向服務(wù)端發(fā)數(shù)據(jù),需要對數(shù)據(jù)進行掩碼操作,服務(wù)端向客戶端發(fā)數(shù)據(jù),不需要對數(shù)據(jù)進行掩碼操作。
如果Mask為1,則Masking-key中會定義一個掩碼鍵,通過該掩碼鍵對數(shù)據(jù)載荷進行反掩碼??蛻舳税l(fā)送給服務(wù)端的數(shù)據(jù)幀,MASK都是1。
Payload len 為7位,或7+16位,或7+64位,表示數(shù)據(jù)載荷的長度,單位字節(jié)。
如果Payload len=0~125,表示,數(shù)據(jù)的長度為0~125字節(jié)。
如果Payload len=126,表示,后續(xù)的2個字節(jié)代表一個16位的無符號整數(shù),該整數(shù)表示數(shù)據(jù)的長度。
如果Payload len=127,表示,后續(xù)的8個字節(jié)代表一個64位的無符號整數(shù),該整數(shù)表示數(shù)據(jù)的長度。
如果Payload len占用多個字節(jié),Payload len的二進制表達采用Big-endian。
Masking-key 占0或32位,客戶端向服務(wù)端發(fā)送數(shù)據(jù)幀,數(shù)據(jù)載荷都進行了掩碼操作,Mask為1,且?guī)Я?字節(jié)的Masking-key。如果Mask為0,則沒有Masking-key。
注意數(shù)據(jù)載荷的長度,不包括Masking-key的長度。
6、掩碼的算法
Masking-key掩碼鍵是由客戶端生成的32位隨機數(shù),掩碼操作不會影響數(shù)據(jù)載荷的長度。
function unmask(buffer, mask) { const length = buffer.length; for (var i = 0; i < length; i++) { buffer[i] ^= mask[i & 3]; } }
7、實現(xiàn)websocket的握手
const crypto = require('crypto'); const net = require('net'); //計算websocket校驗 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)建一個tcp服務(wù)器 let server = net.createServer(function (socket) { socket.once('data', function (data) { data = data.toString(); //查看請求頭中是否有升級websocket協(xié)議的頭信息 if (data.match(/Upgrade: websocket/)) { let rows = data.split('\r\n'); //去掉第一行的請求行 //去掉請求頭的尾部兩個空行 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']; //計算websocket校驗 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'); //給客戶端發(fā)送響應(yīng)內(nèi)容 socket.write(res); //注意這里不要斷開連接,繼續(xù)監(jiān)聽'data'事件 socket.on('data', function (buffer) { //注意buffer的最小單位是一個字節(jié) //取第一個字節(jié)的第一位,判斷是否是結(jié)束位 let fin = (buffer[0] & 0b10000000) === 0b10000000; //取第一個字節(jié)的后四位,得到的一個是十進制數(shù) let opcode = buffer[0] & 0b00001111; //取第二個字節(jié)的第一位是否是1,判斷是否掩碼操作 let mask = buffer[1] & 0b100000000 === 0b100000000; //載荷數(shù)據(jù)的長度 let payloadLength = buffer[1] & 0b01111111; //掩碼鍵,占4個字節(jié) let maskingKey = buffer.slice(2, 6); //載荷數(shù)據(jù),就是客戶端發(fā)送的實際數(shù)據(jù) let payloadData = buffer.slice(6); //對數(shù)據(jù)進行解碼處理 unmask(payloadData, maskingKey); //向客戶端響應(yīng)數(shù)據(jù) let send = Buffer.alloc(2 + payloadData.length); //0b10000000表示發(fā)送結(jié)束 send[0] = opcode | 0b10000000; //載荷數(shù)據(jù)的長度 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)聽8888端口 server.listen(8888);
index.html的代碼:
Document
希望本文所述對大家node.js程序設(shè)計有所幫助。