Buffer
是node
提供的一個操做二進制流的類。它可以讓開發者以相似操做數組的方式去操做二進制數據。事實上,es6
也提供了定型數組來操做二進制數據,後文會簡單對比下二者區別。javascript
假設html
a=0001 0101(2) b=0101 1010(2)
1.|
位或, 對應位有一個爲1則結果爲1java
a | b = 0101 1111(2)
2.&
位與,對應位都爲1結果才爲1node
a & b = 0001 0000(2)
3.~
位非, 對應位1變0,0變1es6
~a = 1110 1010(2) console.log('~a',(~a).toString(2)); js中運行結果-10110 也就是1001 0110; 這個過程多是,求位非後獲得111 01010,發現是個負數,存它的補碼即1001 0110,讀到的就是這個值了
4.>>
向右移位 全部位像左移指定的位數,左邊超出的捨棄,右邊不足的補0web
a >> 2 = 0000 0101(2)
5.<<
向左移位 全部位像左移指定的位數,左邊超出的捨棄,右邊不足的補0segmentfault
a << 2 = 0101 0100(2)
java 裏有帶符號右移和帶符號左移,js裏是沒有這種操做的
6.^
異或 對應位相同則爲0,不然爲1api
a ^ b = 01001111
7.一些操做數組
a & 0xf
a >> 4
(a & 0x20) >> 5
其餘的方式本身探索去吧
關於位運算,能夠參看https://blog.csdn.net/foreverling_ling/article/details/61417649瀏覽器
node v6.0
以前能夠用new Buffer()
建立, 之後則用以下幾個方法建立
關於這些方法的具體用法可參考文檔,這裏簡單演示一些基礎用法. 須要注意的是,
Buffer中的一項是一個字節(8位)。log出來每一個項用兩個16進制數表示。
let buf1 = Buffer.alloc(8); let buf2 = Buffer.alloc(8,1); let buf3 = Buffer.alloc(8,'a'); let buf4 = Buffer.alloc(8, 'a', 'utf8'); let buf5 = Buffer.allocUnsafe(8); let buf6 = Buffer.allocUnsafeSlow(8); console.log('buf1', buf1); // buf1 <Buffer 00 00 00 00 00 00 00 00> console.log('buf2', buf2); // buf2 <Buffer 01 01 01 01 01 01 01 01> console.log('buf3', buf3); // buf3 <Buffer 61 61 61 61 61 61 61 61> console.log('buf4', buf4); // buf4 <Buffer 61 61 61 61 61 61 61 61> console.log('buf5', buf5); // buf5 <Buffer c0 1d e0 03 01 00 00 00> console.log('buf6', buf6); // buf6 <Buffer 00 00 00 00 00 00 00 00> // 注意buf6與buf1對比 let buf7 = Buffer.from([10,0x61,0b10,'a']); console.log('buf7', buf7); // buf7 <Buffer 0a 61 02 00> let arrayBuffer = new Uint16Array(2); arrayBuffer[0] = 10; arrayBuffer[1] = 12; console.log('arrayBuffer.byteLength', arrayBuffer.byteLength); // arrayBuffer.byteLength 4 // 這樣寫是不共享內存的, 新開闢了一塊內存, 並且去掉了爲0的字節 let buf8 = Buffer.from(arrayBuffer); console.log('buf8', buf8); // buf8 <Buffer 0a 0c> arrayBuffer[0] = 11; console.log('buf8', buf8); // buf8 <Buffer 0a 0c> // 這樣作跟ArrayBuffer是共享內存的 let buf9 = Buffer.from(arrayBuffer.buffer); console.log('buf9', buf9); // buf9 <Buffer 0b 00 0c 00> arrayBuffer[1] = 14; console.log('buf9', buf9); // buf9 <Buffer 0b 00 0e 00> let buf10 = Buffer.alloc(8, 1); // 從Buffer建立Buffer也是會新開闢內存的, 不共享 let buf11 = Buffer.from(buf10); console.log('buf11', buf11); // buf11 <Buffer 01 01 01 01 01 01 01 01> buf10[0] = 2; console.log('buf10', buf10); // buf10 <Buffer 02 01 01 01 01 01 01 01> console.log('buf11', buf11); // buf11 <Buffer 01 01 01 01 01 01 01 01>
具體用法可參考文檔,這裏比較下有U和無U, BE和LE
1.BE和LE
認爲低地址存的是高位
相反,認爲低地址存的是低位
大端序用的多一點,符合習慣
let buf = Buffer.from([0x01, 0x02, 0x03, 0x01, 0x02]); console.log(buf.readInt16BE(0)); // 258 console.log(buf.readInt16LE(0)); // 513 console.log(0x0102); // 258 console.log(0x0201); // 513
2.有U和無U
主要是看第一位算不算符號位,無U的話第一位是算符號位的,有U第一位算數據位
let buf2 = Buffer.from([0xff]); console.log(buf2.readInt8(0)); // -1 console.log(buf2.readUInt8(0)); // 255
注意下上邊的offset是偏移多少字節,單位字節
與讀操做相似,不作演示
1.訪問和寫入
buf[0] // 訪問第一個字節 buf[0] = 1 // 將第一個字節寫爲1
2.slice方法
從一個buffer中截取一段生成一個buffer,新舊buffer是共享內存的,和Array不一樣,Array的這個方法會新開闢內存。
let buf = Buffer.from([0x01, 0x02, 0x03]); console.log(buf); // <Buffer 01 02 03> buf[0] = 0x5; console.log(buf[0]); // 5 let buf2 = buf.slice(0,2); console.log(buf2); // <Buffer 05 02> buf[1] = 0x8; console.log(buf2); // <Buffer 05 08> let arr = [0,1,2,3]; let arr2 = arr.slice(0,2); arr[0] = 10; console.log(arr2); // [0, 1]
3.concat方法
鏈接多個buffer生成一個buffer, 不共享內存
let buf3 = Buffer.alloc(4,1); let buf4 = Buffer.alloc(4,5); let totalLength = buf3.length + buf4.length; const bufA = Buffer.concat([buf3, buf4], totalLength); console.log(bufA); // <Buffer 01 01 01 01 05 05 05 05> buf3[0] = 3; console.log(bufA); // <Buffer 01 01 01 01 05 05 05 05>
關於Buffer更深刻的理解能夠參考
ArrayBuffer在ES6中被引入,能夠認爲是必定大小的內存空間。對這段內存的操做則須要視圖支持,包括通用的視圖DataView
和定型數組1TypedArray
。
let buffer1 = new ArrayBuffer(8); let view1 = new DataView(buffer1); console.log(buffer1); // ArrayBuffer { byteLength: 8 } console.log(view1); // DataView { // byteLength: 8, // byteOffset: 0, // buffer: ArrayBuffer { byteLength: 8 } } view1.setInt8(0,3); console.log(view1.getInt8(0)); // 3 let buffer2 = buffer1.slice(1,2); let view2 = new DataView(buffer2); view1.setInt8(1,3); console.log(view2.getInt8(0)); // 0
1.屬性
2.寫操做
3.讀操做
1.類型
建立方法
2.方法
typedArray[0] = 1 console.log(typedArray[0])
更多類數組方法參考:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
let buffer1 = new ArrayBuffer(10); let int8Array = new Int8Array(buffer1); int8Array[0] = 1; console.log(int8Array.length); // 10 console.log(int8Array.byteLength); // 10 console.log(int8Array[0]); // 1 let int16Array = new Int16Array(buffer1); console.log(int16Array.length); // 5 console.log(int16Array.byteLength); // 10
Array
是不能去操做位的,可是Buffer
和TypedArray
是能夠的Buffer
的slice
方法是在原來的內存上直接截取的, 會共享一段內存,而Array
和TypedArray
是會拷貝一份放到一段新的內存Buffer
是Uint8Array
的實例,buf instanceOf Unint8Array=true
Buffer
和Array
都沒有byteLength
方法,而TypedArray
有,byteLength
表示TypedArray
佔用的字節數,length
表示的是有多少個項Array
數據項沒有必定的數據類型,Buffer
的每一項都是1字節數字,TypedArray
有多種類型https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
1.介紹
WebSocket
協議是基於TCP
的一種新的網絡協議。它實現了瀏覽器與服務器全雙工(full-duplex)通訊——容許服務器主動發送信息給客戶端。
2.鏈接(握手)
客戶端 -> 發一個特殊的http請求 -> 服務器發一個特殊的http響應 -> 鏈接成功
鏈接請求
下面是個實際的請求頭
GET ws://127.0.0.1:3000/ HTTP/1.1 Host: 127.0.0.1:3000 Connection: Upgrade Pragma: no-cache Cache-Control: no-cache User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 Upgrade: websocket Origin: http://localhost:3000 Sec-WebSocket-Version: 13 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Sec-WebSocket-Key: HfqW8RyI8GitR89fzjbGgA== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
一個普通的請求頭以下
GET / HTTP/1.1 Host: localhost:3000 Connection: keep-alive Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9
關於創建鏈接的請求不詳細說,重點關注的服務端的部分,使用h5的websocket時,瀏覽器已經幫咱們作好了一切。想了解更多細節的同窗能夠參考
鏈接響應
下面是一個實際的響應
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: mG8+Ke3Gs4TDeff7HYfHmoXPkrA=
在node
中,使用createServer
會返回一個http.Server
類的實例,當客戶端對這個server
發起一個Connection
爲Upgrade
的請求時,server
會觸發一個upgrade
事件,咱們能夠在這個事件處理函數中多客戶端發來的信息進行校驗, 校驗成功能夠按協議給出響應即創建鏈接成功了。
const crypto = require('crypto'); const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; // GUID server.on('upgrade', (req, socket) => { let key = req.headers['sec-websocket-key']; key = crypto .createHash('sha1') .update(key + GUID) .digest('base64'); let resHeaders = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', 'Sec-WebSocket-Accept: ' + key, '', '' ]; resHeaders = resHeaders.join('\r\n'); socket.write(resHeaders); });
上述代碼忽略了對請求頭字段的校驗。
Sec-WebSocket-Accept
這個字段是須要計算的。
須要將請求頭的sec-websocket-key
字段 與一個規定的字符串鏈接,再使用sha1
加密,再將這個加密後的字符串轉成base64
便可獲得。
3.幀結構
一個數據塊可能被拆成多個數據幀發送, FIN標示是否爲最後一個數據幀, 只有一個數據幀也要置爲1
除非兩端協商了非0值的含義, 不然必須都爲0; 否則另外一端要中斷鏈接
定義數據部分的信息, 無效終止鏈接; 0x8標示是個關閉鏈接的幀
標示數據部分是否使用掩碼
0-125: 7bits
126 2B
127 8B
從客戶端發送的數據會包含這個掩碼
x= 0B or xB, 除非已經和客戶端肯定擴展數據含義, 不然不能有
y 應用數據
4.斷開
略了
// 這裏邊還有不少問題 const EventEmitter = require('events'); const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; // GUID class Websocket extends EventEmitter { constructor(p) { super(); if(typeof p === 'number') { // 表示不是掛在http服務上的 // 略了, 能夠用net 模塊根據指定端口建立tcp鏈接 }else if(typeof p === 'object') { // 表示與http服務公用一個服務 this.server = p; this._init(); } else { throw new Error('error'); } this.socketsMap = new Map(); } _handshake(req, socket) { this.socket = socket; let key = req.headers['sec-websocket-key']; key = require('crypto') .createHash('sha1') .update(key + GUID) .digest('base64'); let resHeaders = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', 'Sec-WebSocket-Accept: ' + key, '', '' ]; socket.on('data', frame => { const decodedFrame = this._decode(frame); if(decodedFrame.opcode === 0x0) { } const opcode = decodedFrame.opcode; if(opcode === 0x8) { this.emit('close', socket); socket.write(this.closeFrame()); this.socketsMap.delete(socket); } else { decodedFrame.data && this.emit('message', socket, decodedFrame.data.toString('utf8')); } }); resHeaders = resHeaders.join('\r\n'); socket.write(resHeaders); this.socketsMap.set(socket, 1); this.emit('open', socket); } _init() { this.server.on('upgrade', (req, socket) => { this._handshake(req, socket); }) } send(socket, msg) { try { socket.write(this._encode(msg)); } catch(e) { console.log(e); } } broadcast(msg) { for (const socket of this.socketsMap) { this.send(socket[0], msg); } } /** * 解碼 */ _decode(frame) { console.log(frame) let frame1 = frame[0]; // 第一個字節 let FIN = frame1 >> 7; // 標示是否爲結束幀 // 擴展 let RSV1 = frame1 >> 6 & 0b01; let RSV2 = frame1 >> 5 & 0b001; let RSV3 = frame1 >> 4 & 0b0001; let opcode = frame1 & 0x0F; // 標示數據信息類型 let MASKING_KEY_buf; // 掩碼 let data; // 數據 let frame2 = frame[1]; // 第二個字節 let MASK = frame2 >> 7; let payloadLength = frame2 & 0x7F; console.log(payloadLength) let extendPayloadBytes = 0; if (payloadLength === 126) { payloadLength = frame.readUInt16BE(2); extendPayloadBytes = 2; } else if (payloadLength === 127) { payloadLength = frame.readUInt32BE(2); extendPayloadBytes = 8; } if (MASK === 1) { MASKING_KEY_buf = frame.slice(2 + extendPayloadBytes, 6 + extendPayloadBytes); data = Buffer.alloc(payloadLength); for (let i = 0; i < payloadLength; i++) { var j = i % 4; data[i] = frame[2 + extendPayloadBytes + 4 + i] ^ MASKING_KEY_buf[j]; } } return { FIN, RSV1, RSV2, RSV3, opcode, MASKING_KEY_buf, data } } closeFrame() { let f = Buffer.from([0x8, 0x8]); console.log(f); return f; } _encode(data) { let dataBuf = Buffer.from(data, 'utf8'); let dataLength = dataBuf.length; // 數據長度,bytes let frames = []; let preInfoArr = []; preInfoArr.push((1 << 7) + 1); // FIN和opcode if (dataLength < 126) { preInfoArr.push((0 << 7) + dataLength); // mask 和數據長度 let f = Buffer.from(preInfoArr); dataBuf = Buffer.concat([f,dataBuf]); frames.push(dataBuf); // 數據 dataLength = 0; } else if (dataLength < Math.pow(2, 16)) { preInfoArr.push((0 << 7) + 126); // 佔位,表示要用後面兩個字節標示長度 preInfoArr.push(((dataLength & 0xFF00) >> 8), (dataLength & 0xFF)); let f = Buffer.from(preInfoArr); dataBuf = f.concat(dataBuf) frames.push(dataBuf); } else if (dataLength < Math.pow(2, 32)) { preInfoArr.push((0 << 7) + 126); // 佔位,表示要用後面兩個字節標示長度 preInfoArr.push(0x0,0x0); preInfoArr.push( (dataLength & 0xFF000000) >> 24, (dataLength & 0xFF0000) >> 16, (dataLength & 0xFF00) >> 8, dataLength & 0xFF ) let f = Buffer.from(preInfoArr); dataBuf = Buffer.concat([f,dataBuf]); frames.push(dataBuf); } else { // 須要分片了 // 暫不考慮了 } return frames[0]; } } module.exports = Websocket;