成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Node.js - 200 多行代碼實(shí)現(xiàn) Websocket 協(xié)議

張巨偉 / 758人閱讀

摘要:預(yù)備工作序最近正在研究相關(guān)的知識(shí),想著如何能自己實(shí)現(xiàn)協(xié)議。監(jiān)聽事件就是協(xié)議的抽象,直接在上面監(jiān)聽已有的事件和事件這兩個(gè)事件。表示當(dāng)前數(shù)據(jù)幀為消息的最后一個(gè)數(shù)據(jù)幀,此時(shí)接收方已經(jīng)收到完整的消息,可以對(duì)消息進(jìn)行處理。

A、預(yù)備工作 1、序

最近正在研究 Websocket 相關(guān)的知識(shí),想著如何能自己實(shí)現(xiàn) Websocket 協(xié)議。到網(wǎng)上搜羅了一番資料后用 Node.js 實(shí)現(xiàn)該協(xié)議,倒也沒有想象中那么復(fù)雜,除去注釋語句和 console 語句后,大約 200 行代碼左右。本文記錄了實(shí)現(xiàn)過程中的經(jīng)驗(yàn)和總結(jié)。

如果你想要寫一個(gè) WebSocket 服務(wù)器,首先需要讀懂對(duì)應(yīng)的網(wǎng)絡(luò)協(xié)議 RFC6455,不過這對(duì)于一般人來說有些 “晦澀”,英文且不說,還得咬文嚼字理解 網(wǎng)絡(luò)編程 含義。

好在 WebSocket 技術(shù)出現(xiàn)比較早,所以可以搜到 RFC6455 中文版,網(wǎng)上也有很多針對(duì)該協(xié)議的剖析文章,很多文章里還有現(xiàn)成的實(shí)現(xiàn)代碼可以參考,所以說實(shí)現(xiàn)一個(gè)簡單的 Websocket 服務(wù)并非難事。

本文更偏向?qū)崙?zhàn)(in action),會(huì)從知識(shí)儲(chǔ)備、具體代碼分析以及注意事項(xiàng)角度去講解如何用 Node.js 實(shí)現(xiàn)一個(gè)簡單的 Websocket 服務(wù),至于 Websocket 概念、定義、解釋和用途等基礎(chǔ)知識(shí)不會(huì)涉及,因?yàn)檫@些知識(shí)在本文所列的參考文章中輕松找到。(也可以自行網(wǎng)上隨便一搜,就能找到很多)

2、知識(shí)儲(chǔ)備

如果要自己寫一個(gè) Websocket 服務(wù),主要有兩個(gè)難點(diǎn):

熟練掌握 Websocket 的協(xié)議,這個(gè)需要多讀現(xiàn)有的解讀類文章;(下面會(huì)給出參考文章)

操作二進(jìn)制數(shù)據(jù)流,在 Node.js 中需要對(duì) Buffer 這個(gè)類稍微熟悉些。

同時(shí)還需要具備兩個(gè)基礎(chǔ)知識(shí)點(diǎn):

網(wǎng)絡(luò)編程中使用 大端次序(Big endian)表示大于一字節(jié)的數(shù)據(jù),稱之為 網(wǎng)絡(luò)字節(jié)序 (不曉得大小端的,推薦閱讀 什么是大小端?)

了解最高有效位(MSB, Most Significant Bit),不太清楚的,可以參考 LSB最低有效位和MSB最高有效位

具體的做法如下,推薦先閱讀以下幾篇參考文章:

學(xué)習(xí)WebSocket協(xié)議—從頂層到底層的實(shí)現(xiàn)原理(修訂版):作者本身自己就用 Node.js 實(shí)現(xiàn)過一遍,知識(shí)點(diǎn)講解挺透徹的,適合前端同學(xué)優(yōu)先閱讀

WebSocket詳解(一):初步認(rèn)識(shí)WebSocket技術(shù):是一系列的文章,從淺入深,配有豐富的圖文

WebSocket:5分鐘從入門到精通:全文以 Q&A 的方式組織而成,協(xié)議的要點(diǎn)都解讀到了,除此之外還很全面, 涉及了WebSocket如何建立連接、交換數(shù)據(jù)的細(xì)節(jié)、數(shù)據(jù)幀的格式以及網(wǎng)絡(luò)安全等。

MDN - Writing WebSocket servers:MDN 官方教程,讀一遍沒啥壞處。

然后開始寫代碼,在實(shí)現(xiàn)過程中的大部分代碼可以從下面 3 篇文章中找到并借鑒(copy):

nodejs 實(shí)現(xiàn):簡化版本的從這兒借鑒過來的

學(xué)習(xí)WebSocket協(xié)議—從頂層到底層的實(shí)現(xiàn)原理(修訂版)

WebSocket協(xié)議解析:雖然是 C++ 寫的,但不影響代碼邏輯的理解

閱讀完上面的文章,你會(huì)有發(fā)現(xiàn)一個(gè)共同點(diǎn),就是在實(shí)現(xiàn) WebSockets 過程中,最最核心的部分就是 解析 或者 生成 Frame(幀),就是下面這結(jié)構(gòu):

截圖來自規(guī)范Base Framing Protocol

想要理解 frame 各個(gè)字段的含義,可參考 WebSocket詳解(三):深入WebSocket通信協(xié)議細(xì)節(jié),文中作者繪制了一副圖來解釋這個(gè) frame 結(jié)構(gòu);

而在代碼層面,frame 的解析或生成可以在 RocketEngine - parser 或者 _processBuffer 中找到。

在完成上面幾個(gè)方面的知識(shí)儲(chǔ)備之后,而且大多有現(xiàn)成的代碼,所以自己邊抄邊寫一個(gè) Websocket 服務(wù)器并不算太難。

對(duì)于 Websocket 初學(xué)者,請務(wù)必閱讀以上參考文章,對(duì) Websocket 協(xié)議有大概的了解之后再繼續(xù)本文剩下部分的閱讀,否則很有可能會(huì)覺得我寫得云里霧里,不知所云。

B、 實(shí)戰(zhàn)

實(shí)現(xiàn)代碼放在自己的 demos 倉庫的 micro-ws 的目錄 了,git clone 后本地運(yùn)行,執(zhí)行

node index.js

將會(huì)在 http://127.0.0.1:3000 創(chuàng)建服務(wù)。運(yùn)行服務(wù)之后,打開控制臺(tái)就能看到效果:

動(dòng)圖中瀏覽器 console 所執(zhí)行的 js 代碼步驟如下:

1.先建立連接

var ws = new WebSocket("ws://127.0.0.1:3000");
ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
};

2.然后發(fā)送消息:(注意一定要在建立連接之后再執(zhí)行該語句,否則發(fā)不出消息的)

ws.send("hello world"); 

從效果可見,我們已經(jīng)實(shí)現(xiàn) Websocket 最基本的通訊功能了。

接下來我們詳細(xì)看一下具體實(shí)現(xiàn)的細(xì)節(jié)。

1、調(diào)用所寫的 Websocket 類

站在使用者的角度,假設(shè)我們已經(jīng)完成 Websocket 類了,那么應(yīng)該怎么使用?

客戶端通過 HTTP Upgrade 請求,即 101 Switching Protocol 到 HTTP 服務(wù)器,然后由服務(wù)器進(jìn)行協(xié)議轉(zhuǎn)換。

在 Node.js 中我們通過 http.createServer 獲取 http.server 實(shí)例,然后監(jiān)聽 upgrade 事件,在處理這個(gè)事件:

// HTTP服務(wù)器部分
var server = http.createServer(function(req, res) {
  res.end("websocket test
");
});

// Upgrade請求處理
server.on("upgrade", function(req, socket, upgradeHead){
  // 初始化 ws
  var ws = new WebSocket(req, socket, upgradeHead);
  
  // ... ws 監(jiān)聽 data、error 的邏輯等

});

這里監(jiān)聽 upgrade 事件的回調(diào)函數(shù)中第二個(gè)參數(shù) socket 是 net.Socket 實(shí)例,這個(gè)類是 TCP 或 UNIX Socket 的抽象,同時(shí)一個(gè) net.Socket 也是一個(gè) duplex stream,所以它能被讀或?qū)?,并且它也是一個(gè) EventEmitter。

我們就利用這個(gè) socket 對(duì)象上進(jìn)行 Websocket 類實(shí)例的初始化工作;

2、構(gòu)造函數(shù)

所以不難理解 Websocket 的構(gòu)造函數(shù)就是下面這個(gè)樣子:

class WebSocket extends EventEmitter {
  constructor(req, socket, upgradeHead){
    super(); // 調(diào)用 EventEmitter 構(gòu)造函數(shù)
    
    // 1. 構(gòu)造響應(yīng)頭 resHeaders 部分
    
    // 2. 監(jiān)聽 socket 的 data 事件,以及 error 事件
    
    // 3. 初始化成員屬性
    
  }
}

注意,我們需要繼承內(nèi)置的 EventEmitter ,這樣生成的實(shí)例才能監(jiān)聽、綁定事件;

Node.js 采用事件驅(qū)動(dòng)、異步編程,天生就是為了網(wǎng)絡(luò)服務(wù)而設(shè)計(jì)的,繼承 EventEmitter 就能享受到非阻塞模式的 IO 處理;

講一下其中 響應(yīng)頭的構(gòu)造事件監(jiān)聽 部分。

2.1、返回響應(yīng)頭(Response Header)

根據(jù)協(xié)議規(guī)范,我們能寫出響應(yīng)頭的內(nèi)容:

Sec-WebSocket-Key258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接。

通過 SHA1 計(jì)算出摘要,并轉(zhuǎn)成 base64 字符串。

具體代碼如下:

    var resKey = hashWebSocketKey(req.headers["sec-websocket-key"]);

    // 構(gòu)造響應(yīng)頭
    var resHeaders = [
      "HTTP/1.1 101 Switching Protocols",
      "Upgrade: websocket",
      "Connection: Upgrade",
      "Sec-WebSocket-Accept: " + resKey
    ]
      .concat("", "")
      .join("
");
    socket.write(resHeaders);

當(dāng)執(zhí)行 socket.write(resHeaders); 到后就和客戶端建立起 WebSocket 連接了,剩下去就是數(shù)據(jù)的處理。

2.2、監(jiān)聽事件

socket 就是 TCP 協(xié)議的抽象,直接在上面監(jiān)聽已有的 data 事件和 close 事件這兩個(gè)事件。

還有其他事件,比如 error、end 等,詳細(xì)參考 net.Socket 文檔
    socket.on("data", data => {
      this.buffer = Buffer.concat([this.buffer, data]);
      while (this._processBuffer()) {} // 循環(huán)處理返回的 data 數(shù)據(jù)
    });

    socket.on("close", had_error => {
      if (!this.closed) {
        this.emit("close", 1006);
        this.closed = true;
      }
    });

close 的事件邏輯比較簡單,比較重要的是 data 的事件監(jiān)聽部分。核心就是 this._processBuffer() 這個(gè)方法,用于處理客戶端傳送過來的數(shù)據(jù)(即 Frame 數(shù)據(jù))
。注意該方法是放在 while 循環(huán)語句里,處理好邊界情況,防止死循環(huán)。

3、Frame 幀數(shù)據(jù)的處理

WebSocket 客戶端、服務(wù)端通信的最小單位是幀(frame),由1個(gè)或多個(gè)幀組成一條完整的消息(message)。
this._processBuffer() 部分代碼邏輯就是用來解析幀數(shù)據(jù)的,所以它是實(shí)現(xiàn) Websocket 代碼的關(guān)鍵;(該方法里面用到了大量的位操作符以及 Buffer 類的操作)

幀數(shù)據(jù)結(jié)構(gòu)詳細(xì)定義可參考 RFC6455 5.2節(jié),上面羅列的參考文章都有詳細(xì)的解讀,我在這兒也不啰嗦講細(xì)節(jié)了,直接看代碼比聽我用文字講要好。

這里就其中兩個(gè)細(xì)節(jié)需要鋪墊一下,方便更好地理解代碼。

3.1、操作碼(Opcode)

Opcode操作代碼,Opcode 的值決定了應(yīng)該如何解析后續(xù)的數(shù)據(jù)載荷(data payload)

根據(jù) Opcode 我們可以大致將數(shù)據(jù)幀分成兩大類:數(shù)據(jù)幀控制幀。

數(shù)據(jù)幀:目前只有 3 種,對(duì)應(yīng)的 opcode 是:

0x0:數(shù)據(jù)延續(xù)幀

0x1:utf-8文本

0x2:二進(jìn)制數(shù)據(jù);

0x3 - 0x7:目前保留,用于后續(xù)定義的非控制幀。

控制幀:除了上述 3 種數(shù)據(jù)幀之外,剩下的都是控制幀

0x8:表示連接斷開

0x9:表示 ping 操作

0xA:表示 pong 操作

0xB - 0xF:目前保留,用于后續(xù)定義的控制幀

在代碼里,我們會(huì)先從幀數(shù)據(jù)中提取操作碼:

var opcode = byte1 & 0x0f; //截取第一個(gè)字節(jié)的后 4 位,即 opcode 碼

然后根據(jù)協(xié)議獲取到真正的數(shù)據(jù)載荷(data payload),然后將這兩部分傳給 _handleFrame 方法:

this._handleFrame(opcode, payload); // 處理操作碼

該方法會(huì)根據(jù)不同的 opcode 做出不同的操作:

_handleFrame(opcode, buffer) {
    var payload;
    switch (opcode) {
      case OPCODES.TEXT:
        payload = buffer.toString("utf8"); //如果是文本需要轉(zhuǎn)化為utf8的編碼
        this.emit("data", opcode, payload); //Buffer.toString()默認(rèn)utf8 這里是故意指示的
        break;
      case OPCODES.BINARY: //二進(jìn)制文件直接交付
        payload = buffer;
        this.emit("data", opcode, payload);
        break;
      case OPCODES.PING: // 發(fā)送 pong 做響應(yīng)
        this._doSend(OPCODES.PONG, buffer);
        break;
      case OPCODES.PONG: //不做處理
        console.log("server receive 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, "unhandle opcode:" + opcode);
    }
  }
3.2、分片(Fragment)
規(guī)范文檔:5.4 -  Fragmentation

一旦 WebSocket 客戶端、服務(wù)端建立連接后,后續(xù)的操作都是基于數(shù)據(jù)幀的傳遞。理論上來說,每個(gè)幀(Frame)的大小是沒有限制的。

對(duì)于大塊的數(shù)據(jù),Websocket 協(xié)議建議對(duì)數(shù)據(jù)進(jìn)行分片(Fragment)操作。

分片的意義主要是兩方面:

主要目的是允許當(dāng)消息開始但不必緩沖該消息時(shí)發(fā)送一個(gè)未知大小的消息。如果消息不能被分片,那么端點(diǎn)將不得不緩沖整個(gè)消息以便在首字節(jié)發(fā)生之前統(tǒng)計(jì)出它的長度。對(duì)于分片,服務(wù)器或中間件可以選擇一個(gè)合適大小的緩沖,當(dāng)緩沖滿時(shí),再寫一個(gè)片段到網(wǎng)絡(luò)。

另一方面分片傳輸也能更高效地利用多路復(fù)用提高帶寬利用率,一個(gè)邏輯通道上的一個(gè)大消息獨(dú)占輸出通道是不可取的,因此多路復(fù)用需要可以分割消息為更小的分段來更好的共享輸出通道。參考文檔 I/O多路復(fù)用技術(shù)(multiplexing)是什么?

WebSocket 協(xié)議提供的分片方法,是將原本一個(gè)大的幀拆分成數(shù)個(gè)小的幀。下面是把一個(gè)大的Frame分片的圖示:

分片編號(hào) 0 1 ... n-2 n-1
FIN 0 0 ... 0 1
opcode !0 0 0 0 0

由圖可知,第一個(gè)分片的 FIN 為 0,Opcode 為非0值(0x1 或 0x2),最后一個(gè)分片的FIN為1,Opcode為 0。中間分片的 FINopcode 二者均為 0。

根據(jù) FIN 的值來判斷,是否已經(jīng)收到消息的最后一個(gè)數(shù)據(jù)幀。

FIN=1 表示當(dāng)前數(shù)據(jù)幀為消息的最后一個(gè)數(shù)據(jù)幀,此時(shí)接收方已經(jīng)收到完整的消息,可以對(duì)消息進(jìn)行處理。

FIN=0,則接收方還需要繼續(xù)監(jiān)聽接收其余的數(shù)據(jù)幀。

opcode在數(shù)據(jù)交換的場景下,表示的是數(shù)據(jù)的類型。

0x01 表示文本,永遠(yuǎn)是 utf8 編碼的

0x02 表示二進(jìn)制

0x00 比較特殊,表示 延續(xù)幀(continuation frame),顧名思義,就是完整消息對(duì)應(yīng)的數(shù)據(jù)幀還沒接收完。

代碼里,我們需要檢測 FIN 的值,如果為 0 說明有分片,需要記錄第一個(gè) FIN 為 0 時(shí)的 opcode 值,緩存到 this.frameOpcode 屬性中,將載荷緩存到 this.frames 屬性中:

    var FIN = byte1 & 0x80; // 如果為0x80,則標(biāo)志傳輸結(jié)束,獲取高位 bit
    // 如果是 0 的話,說明是延續(xù)幀,需要保存好 opCode
    if (!FIN) {
      this.frameOpcode = opcode || this.frameOpcode; // 確保不為 0;
    }
    
    //....
    // 有可能是分幀,需要拼接數(shù)據(jù)
    this.frames = Buffer.concat([this.frames, payload]); // 保存到 frames 中

當(dāng)接收到最后一個(gè) FIN 幀的時(shí)候,就可以組裝后給 _handleFrame 方法:

    if (FIN) {
      payload = this.frames.slice(0); // 獲取所有拼接完整的數(shù)據(jù)
      opcode = opcode || this.frameOpcode; // 如果是 0 ,則保持獲取之前保存的 code
      this.frames = Buffer.alloc(0); // 清空 frames
      this.frameOpcode = 0; // 清空 opcode
      this._handleFrame(opcode, payload); // 處理操作碼
    }
3.3、發(fā)送數(shù)據(jù)幀

上面講的都是接收并解析來自客戶端的數(shù)據(jù)幀,當(dāng)我們想給客戶端發(fā)送數(shù)據(jù)幀的時(shí)候,也得按協(xié)議來。

這部分操作相當(dāng)于是上述 _processBuffer 方法的逆向操作,在代碼里我們使用 encodeMessage 方法(為了簡單起見,我們發(fā)送給客戶端的數(shù)據(jù)沒有經(jīng)過掩碼處理)將發(fā)送的數(shù)據(jù)分裝成數(shù)據(jù)幀的格式,然后調(diào)用 socket.write 方法發(fā)送給客戶端;

  _doSend(opcode, payload) {
    // 1. 考慮數(shù)據(jù)分片
    this.socket.write(
      encodeMessage(count > 0 ? OPCODES.CONTINUE : opcode, payload)
    ); //編碼后直接通過socket發(fā)送

為了考慮分片場景,特意設(shè)置 MAX_FRAME_SIZE 來對(duì)每次發(fā)送的數(shù)據(jù)長度做截?cái)嘧龇制?/p>

    // ...
    var len = Buffer.byteLength(payload);
    // 分片的距離邏輯
    var count = 0;
    // 這里可以針對(duì) payload 的長度做分片
    while (len > MAX_FRAME_SIZE) {
      var framePayload = payload.slice(0, MAX_FRAME_SIZE);
      payload = payload.slice(MAX_FRAME_SIZE);
      this.socket.write(
        encodeMessage(
          count > 0 ? OPCODES.CONTINUE : opcode,
          framePayload,
          false
        )
      ); //編碼后直接通過socket發(fā)送
      count++;
      len = Buffer.byteLength(payload);
    }
  // ...

至此已經(jīng)實(shí)現(xiàn) Websocket 協(xié)議的關(guān)鍵部分,所組裝起來的代碼就能和客戶端建立 Websocket 連接并進(jìn)行數(shù)據(jù)交互了。

4、Q&A 4.1、字符串 “258EAFA5-E914-47DA-95CA-C5AB0DC85B11” 怎么來的?

這個(gè)標(biāo)志性字符串是專門標(biāo)示 Websocket 協(xié)議的 UUID;UUID 是長度為 16-byte(128-bit)的ID,一般以形如f81d4fae-7dec-11d0-a765-00a0c91e6bf6的字符串作為 URN(Uniform Resource Name,統(tǒng)一資源名稱)

UUID 可以移步到 UUID原理 和 RFC 4122 獲取更多知識(shí)

為啥選擇這個(gè)字符串?

在規(guī)范的第七頁已經(jīng)有明確的說明了:

之所以選用這個(gè) UUID ,主要該 ID 極大不太可能被其他不了解 Websocket 協(xié)議的網(wǎng)絡(luò)終端所使用;

我也不曉得該怎么翻譯。。。總之就說這個(gè) ID 就相當(dāng)于 Websocket 協(xié)議的 “身份證號(hào)” 了。

4.2、Websocket 和 HTTP 什么關(guān)系?

HTTP、WebSocket 等應(yīng)用層協(xié)議,都是基于 TCP 協(xié)議來傳輸數(shù)據(jù)的,我們可以把這些高級(jí)協(xié)議理解成對(duì) TCP 的封裝。

既然大家都使用 TCP 協(xié)議,那么大家的連接和斷開,都要遵循 TCP 協(xié)議中的三次握手和四次握手 ,只是在連接之后發(fā)送的內(nèi)容不同,或者是斷開的時(shí)間不同。

對(duì)于 WebSocket 來說,它必須依賴 HTTP 協(xié)議進(jìn)行一次握手 ,握手成功后,數(shù)據(jù)就直接從 TCP 通道傳輸,與 HTTP 無關(guān)了。

4.3、瀏覽器中 Websocket 會(huì)自動(dòng)分片么?

答案是:看具體瀏覽器的實(shí)現(xiàn)

WebSocket是一個(gè) message based 的協(xié)議,它可以自動(dòng)將數(shù)據(jù)分片,并且自動(dòng)將分片的數(shù)據(jù)組裝。
每個(gè) message 可以是一個(gè)或多個(gè)分片。message 不記錄長度,分片才記錄長度;

根據(jù)協(xié)議 websocket 協(xié)議中幀長度上限為 2^63 byte(為 8388608 TB),可以認(rèn)為沒有限制,很明顯按協(xié)議的最大上限來傳輸數(shù)據(jù)是不靠譜的。所以在實(shí)際使用中 websocket 消息長度限制取決于具體的實(shí)現(xiàn)。關(guān)于哲方面,找了兩篇參考文章:

Websocket需要像TCP Socket那樣進(jìn)行邏輯數(shù)據(jù)包的分包與合包嗎?:WebSocket是一個(gè)message-based的協(xié)議,它可以自動(dòng)將數(shù)據(jù)分片,并且自動(dòng)將分片的數(shù)據(jù)組裝;;

websocket長文本問題?:這里給出了長文本 ws 傳輸實(shí)踐總結(jié)。

在文章 WebSocket探秘 中,作者就做了一個(gè)實(shí)驗(yàn),作者發(fā)送 27378 個(gè)字節(jié),結(jié)果被迫分包了;如果是大數(shù)據(jù)量,就會(huì)被socket自動(dòng)分包發(fā)送。

而經(jīng)過我本人試驗(yàn),發(fā)現(xiàn) Chrome 瀏覽器(版本 68.0.3440.106 - 64bit)會(huì)針對(duì) 131072(=2^17)bytes 大小進(jìn)行自動(dòng)分包。我是通過以下測試代碼驗(yàn)證:

var ws = new WebSocket("ws://127.0.0.1:3000");
ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
};
var myArray = new ArrayBuffer(131072 * 2 + 1);
ws.send(myArray);

服務(wù)端日志:

server detect fragment, sizeof payload: 131072
server detect fragment, sizeof payload: 131072
receive data: 2 262145

客戶端日志:

Received Message: good job

截圖如下:

而以同樣的方式去測試一些自己機(jī)器上的瀏覽器:

Firefox(62.0,64bit)

safari (11.1.2 - 13605.3.8)

IE 11

這些客戶端上的 Websocket 幾乎沒有大小的分片(隨著數(shù)據(jù)量增大,發(fā)送會(huì)減緩,但并沒有發(fā)現(xiàn)分片現(xiàn)象)。

5、總結(jié)

從剛開始決定閱讀 Websocket 協(xié)議,到自己使用 Node.js 實(shí)現(xiàn)一套簡單的 Websocket 協(xié)議,到這篇文章的產(chǎn)出,前后耗費(fèi)大約 1 個(gè)月時(shí)間(拖延癥。。。)。
感謝文中所提及的參考文獻(xiàn)所給予的幫助,讓我實(shí)現(xiàn)過程中事半功倍。

之所以能夠使用較少的代碼實(shí)現(xiàn) Websocket,是因?yàn)?Node.js 體系本身了很好的基礎(chǔ),比如其所提供的 EventEmitter 類自帶事件循環(huán),http 模塊讓你直接使用封裝好的 socket 對(duì)象,我們只要按照 Websocket 協(xié)議實(shí)現(xiàn) Frame(幀)的解析和組裝即可。

在使用 Node.js 實(shí)現(xiàn)一遍 Websocket 協(xié)議后,就能較為深刻地理解以下知識(shí)點(diǎn)(理解起來一切都是那么自然而然):

Websocket 是一種應(yīng)用層協(xié)議,是為了提供 Web 應(yīng)用程序和服務(wù)端全雙工通信而專門制定的;

WebSocket 和 HTTP 都是基于 TCP 協(xié)議實(shí)現(xiàn)的;

WebSocket和 HTTP 的唯一關(guān)聯(lián)就是 HTTP 服務(wù)器需要發(fā)送一個(gè) “Upgrade” 請求,即 101 Switching Protocol 到 HTTP 服務(wù)器,然后由服務(wù)器進(jìn)行協(xié)議轉(zhuǎn)換。

WebSocket使用 HTTP 來建立連接,但是定義了一系列新的 header 域,這些域在 HTTP 中并不會(huì)使用;

WebSocket 可以和 HTTP Server 共享同一 port

WebSocket 的 數(shù)據(jù)幀有序

...

本文僅僅是協(xié)議的簡單實(shí)現(xiàn),對(duì)于 Websocket 的其實(shí)還有很多事情可以做(比如支持 命名空間、流式 API 等),有興趣的可以參考業(yè)界流行的 Websocket 倉庫,去練習(xí)鍛造一個(gè)健壯的 Websocket 工具庫輪子:

socketio/socket.io:43.5k star,不多說,業(yè)界權(quán)威龍頭老大。(不過這實(shí)際上不是一個(gè) WebSocket 庫,而是一個(gè)實(shí)時(shí) pub/sub 框架。簡單地說,Socket.IO 只是包含 WebSocket 功能的一個(gè)框架,如果要使用該庫作為 server 端的服務(wù),則 client 也必須使用該庫,因?yàn)樗皇菢?biāo)準(zhǔn)的 WebSocket 協(xié)議,而是基于 WebSocket 再包裝的消息通信協(xié)議)

websockets/ws:9k star,強(qiáng)大易用的 websocket 服務(wù)端、客戶端實(shí)現(xiàn),還有提供很多強(qiáng)大的特性

uNetworking/uWebSockets:9.5k star,小巧高性能的 websocket實(shí)現(xiàn),C++ 寫的,想更多了解 Websocket 的底層實(shí)現(xiàn),該庫是不錯(cuò)的案例。

theturtle32/WebSocket-Node:2.3k star,大部分使用 JavaScript,性能關(guān)鍵部分使用 C++ node-gyp 實(shí)現(xiàn)的庫。其所列的 測試用例 有挺好的參考價(jià)值

本文完。

下面的是我的公眾號(hào)二維碼圖片,歡迎關(guān)注。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/108541.html

相關(guān)文章

  • 「跨域」利用node.js實(shí)踐前端各種跨域方式(下)

    摘要:技術(shù)的學(xué)習(xí)也是如此唯有實(shí)踐才能更清楚的明白原理和加深印象,因此本文會(huì)利用對(duì)前端的各種跨域方式進(jìn)行實(shí)踐,強(qiáng)烈建議一步一步跟著做,相信你肯定會(huì)對(duì)跨域有更深層次的理解。 前言 常言道,讀萬卷書,不如行萬里路。技術(shù)的學(xué)習(xí)也是如此,唯有實(shí)踐才能更清楚的明白原理和加深印象,因此本文會(huì)利用node.js對(duì)前端的各種跨域方式進(jìn)行實(shí)踐,強(qiáng)烈建議一步一步跟著做,相信你肯定會(huì)對(duì)跨域有更深層次的理解。而由于篇...

    Jenny_Tong 評(píng)論0 收藏0
  • 【跨域】跨域的簡易實(shí)現(xiàn)和測試

    摘要:前言由于自己平時(shí)只做做,并沒有遇到太多跨域問題,今天通過幾個(gè)樣例模擬實(shí)現(xiàn)了幾種跨域方式。 前言 由于自己平時(shí)只做做demo,并沒有遇到太多跨域問題,今天通過幾個(gè)樣例模擬實(shí)現(xiàn)了幾種跨域方式。原文地址 傳送門 本文所有樣例靜態(tài)服務(wù)器基于nodejs實(shí)現(xiàn),代碼親測可用。測試步驟如下: 1.為了實(shí)現(xiàn)跨域訪問的效果,需要下載http-server 作為一個(gè)服務(wù)器 npm install http...

    ninefive 評(píng)論0 收藏0
  • 前端常見跨域解決方案(全)

    摘要:需注意的是由于同源策略的限制,所讀取的為跨域請求接口所在域的,而非當(dāng)前頁。目前,所有瀏覽器都支持該功能需要使用對(duì)象來支持,也已經(jīng)成為主流的跨域解決方案。反向代理接口跨域跨域原理同源策略是瀏覽器的安全策略,不是協(xié)議的一部分。 什么是跨域? 跨域是指一個(gè)域下的文檔或腳本試圖去請求另一個(gè)域下的資源,這里跨域是廣義的。 廣義的跨域: 1.) 資源跳轉(zhuǎn): A鏈接、重定向、表單提交 2.) 資源...

    canger 評(píng)論0 收藏0
  • 大話javascript 5期:跨域

    摘要:同源策略所謂同源是指協(xié)議,域名,端口均相同。同源策略是瀏覽器的一個(gè)安全功能,不同源的客戶端腳本在沒有明確授權(quán)的情況下,不能讀寫對(duì)方資源。需注意的是由于同源策略的限制,所讀取的為跨域請求接口所在域的,而非當(dāng)前頁。 一、什么是跨域 1.URL解析 URL (Uniform Resource Locator )統(tǒng)一資源定位符(URL)是用于完整地描述Internet上網(wǎng)頁和其他資源的地址的...

    jzzlee 評(píng)論0 收藏0
  • 基于socket.io快速實(shí)現(xiàn)一個(gè)實(shí)時(shí)通訊應(yīng)用

    摘要:實(shí)時(shí)通訊越來越多應(yīng)用于各個(gè)領(lǐng)域。實(shí)現(xiàn)原生實(shí)現(xiàn)對(duì)象一共支持四個(gè)消息和。是基于的實(shí)時(shí)通信庫。服務(wù)器應(yīng)該用包含相同數(shù)據(jù)的乓包應(yīng)答客戶端發(fā)送探測幀由服務(wù)器發(fā)送以響應(yīng)數(shù)據(jù)包。主要用于在接收到傳入連接時(shí)強(qiáng)制輪詢周期。該間隔可通過配置修改。 隨著web技術(shù)的發(fā)展,使用場景和需求也越來越復(fù)雜,客戶端不再滿足于簡單的請求得到狀態(tài)的需求。實(shí)時(shí)通訊越來越多應(yīng)用于各個(gè)領(lǐng)域。 HTTP是最常用的客戶端與服務(wù)端的...

    venmos 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<