Html5 websocket淺析

初次接觸websocket,究竟它與http協議有何不同,
HTTP 協議有一個缺陷:通信只能由客戶端發起,HTTP 協議做不到服務器主動向客戶端推送信息。
這樣說,如果我想服務器定時推送消息,比如說天氣預報,每隔一段時間就會變化,如何在客戶端獲取最新信息。
之前是使用"輪詢":每隔一段時候,就發出一個詢問,了解服務器有沒有新的信息。最典型的場景就是聊天室。
缺點:輪詢的效率低,非常浪費資源(因為必須不停連接,或者 HTTP 連接始終打開)。

websocket最大的特點:服務器可以主動向客戶端推送信息,客戶端也可以主動向服務器發送信息。

  • 數據格式比較輕量,性能開銷小,通信高效。
  • 可以發送文本,也可以發送二進制數據。
  • 沒有同源限制,客戶端可以與任意服務器通信。

廢話不多講,直接上代碼:

var ws = new WebSocket("[wss://echo.websocket.org](wss://echo.websocket.org/)");
ws.onopen = function(evt) {
  console.log("Connection open ...");
  ws.send("Hello WebSockets!");
};
ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};
ws.onclose = function(evt) { 
 console.log("Connection closed.");
}; 

下面我們來分析會怎么輸出:
1.onopen建立連接后輸出connection open。
2.send發送一條消息,在onmessage事件里監聽,evt.data輸出 hello websockets。
3.close關閉連接,最后輸出connection closed。

websocket狀態

webSocket.readyState

CONNECTING:值為0,表示正在連接。
OPEN:值為1,表示連接成功,可以通信了。
CLOSING:值為2,表示連接正在關閉。
CLOSED:值為3,表示連接已經關閉,或者打開連接失敗。

我們可以在這些特殊時刻做許多事情

switch (ws.readyState) {
  case WebSocket.CONNECTING:
    // do something
    break;
  case WebSocket.OPEN:
    // do something
    break;
  case WebSocket.CLOSING:
    // do something
    break;
  case WebSocket.CLOSED:
    // do something
    break;
  default:
    // this never happens
    break;
}

我們可以給每個事件添加多個函數:

ws.addEventListener('open', function (event) {
   console.log('socket open')
});

ws.addEventListener('open', function (event) {
   ws.send('Hello Server!');
});

代碼示例

這樣吧,我們下面給出一段示例代碼,更能說明問題。
1.下面是一個點擊切換的事件,控制socket的鏈接與釋放。

function ToggleConnectionClicked() {
         if (SocketCreated && (ws.readyState == 0 || ws.readyState == 1)) {  
               ws.close();
           } else {
               Log("準備連接到聊天服務器 ...");
               try {
                ws = 
                new WebSocket("ws://" + document.getElementById("Connection").value);
                 SocketCreated = true;
               } catch (ex) {
                 Log(ex, "ERROR");
                 return;
               }
               document.getElementById("ToggleConnection").innerHTML = "斷開";
               ws.onopen = WSonOpen;
               ws.onmessage = WSonMessage;
               ws.onclose = WSonClose;
               ws.onerror = WSonError;
           }
       };

2.各種事件函數。

function WSonOpen() {
           Log("連接已經建立。", "OK");
           $("#SendDataContainer").show("slow");  //消息發送窗口顯示
       };
 
       function WSonMessage(event) {
           Log(event.data);            
       };
 
       function WSonClose() {
           Log("連接關閉。", "ERROR");
           document.getElementById("ToggleConnection").innerHTML = "連接";
           $("#SendDataContainer").hide("slow");
       };
 
 
       function WSonError() {
           Log("WebSocket錯誤。", "ERROR");
       };

3.當用戶按下發送按鈕,客戶端會調用WebSocket對象向服務器發送信息,并且這個消息會廣播給所有的用戶

function SendDataClicked()
 {
            if (document.getElementById("DataToSend").value != "") {
                ws.send(document.getElementById("txtName").value + "說 :\"" + 
document.getElementById("DataToSend").value + "\"");
                document.getElementById("DataToSend").value = "";
            }
        };

數據類型

注意,服務器數據可能是文本,也可能是二進制數據(blob對象或Arraybuffer對象)。

ws.onmessage = function(event){
  if(typeof event.data === String) {
    console.log("Received data string");
  }

  if(event.data instanceof ArrayBuffer){
    var buffer = event.data;
    console.log("Received arraybuffer");
  }
}

除了動態判斷收到的數據類型,也可以使用binaryType屬性,顯式指定收到的二進制數據類型。

// 收到的是 blob 數據
ws.binaryType = "blob";
ws.onmessage = function(e) {
  console.log(e.data.size);
};

// 收到的是 ArrayBuffer 數據
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
  console.log(e.data.byteLength);
};

可能有同學對blob和arraybuffer(二進制數組)不是很了解,這里我簡單講一下arraybuffer的用法。
ArrayBuffer對象代表儲存二進制數據的一段內存,它不能直接讀寫,只能通過視圖(TypedArray視圖和DataView視圖)來讀寫,視圖的作用是以指定格式解讀二進制數據。

var buf = new ArrayBuffer(32);

上面代碼生成了一段32字節的內存區域,每個字節的值默認都是0。可以看到,ArrayBuffer構造函數的參數是所需要的內存大小(單位字節)。

為了讀寫這段內容,需要為它指定視圖。DataView視圖的創建,需要提供ArrayBuffer對象實例作為參數。

var buf = new ArrayBuffer(32);
var dataView = new DataView(buf);
dataView.getUint8(0) // 0

上面代碼對一段32字節的內存,建立DataView視圖,然后以不帶符號的8位整數格式,讀取第一個元素,結果得到0,因為原始內存的ArrayBuffer對象,默認所有位都是0。

ArrayBuffer有一個靜態方法isView,返回一個布爾值,表示參數是否為ArrayBuffer的視圖實例。這個方法大致相當于判斷參數,是否為TypedArray實例或DataView實例。

var buffer = new ArrayBuffer(8);
ArrayBuffer.isView(buffer) // false

var v = new Int32Array(buffer);
ArrayBuffer.isView(v) // true

大家對這段感興趣的話,可以點 這里.

下面展示了幾種數據的發送:

// Sending String
connection.send('your message');

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(100, 100, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
  binary[i] = img.data[i];
}
connection.send(binary.buffer);

// Sending file as Blob
var file = document.querySelector('input[type="file"]').files[0];
connection.send(file);

我們可以截取canvas繪畫的圖像某一部分,通過二進制數組發送給服務器。

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.rect(10, 10, 100, 100);
ctx.fill();

console.log(ctx.getImageData(50, 50, 100, 100));
// ImageData { width: 100, height: 100, data: Uint8ClampedArray[40000] }

下面分享幾個小tip:
1.How to get the IP address of the client?

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', function connection(ws, req) {
  const ip = req.connection.remoteAddress;
});

獲取遠程客戶端ip。

2.How to detect and close broken connections?

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

function heartbeat() {
  this.isAlive = true;
}

wss.on('connection', function connection(ws) {
  ws.isAlive = true;
  ws.on('pong', heartbeat);
});

const interval = setInterval(function ping() {
  wss.clients.forEach(function each(ws) {
    if (ws.isAlive === false) return ws.terminate();

    ws.isAlive = false;  // 斷開
    ws.ping('', false, true);   //重新ping  如果可以ping通,會接受pong
  });
}, 30000);

上面這個例子檢測了與服務器通信的客戶端,若是有鏈接異常的會被terminate。

3.How to send broadcast to all connected client

const server = http.createServer(app);
    const wss = new WebSocket.Server({ server });

   wss.on('connection', function connection(ws) {
     ws.on('message', function(message) {
       wss.broadcast(message);
     }
   }

   wss.broadcast = function broadcast(msg) {
     console.log(msg);
     wss.clients.forEach(function each(client) {
       client.send(msg);  //這個有所疑問 為什么是send 這個時候不該是receive嗎 
     });
    };

    server.listen(8080, function listening() {
      console.log('Listening on %d', server.address().port);
    });

上面是服務器接收了一個信息后,將該信息發送給所有與之建立連接的client。或許下面這個更適合。

var ws = require("ws");

  global_counter = 0;
  all_active_connections = {};

  ws.createServer(function (websocket) 
  {
      websocket.on('connect', function() 
      {
          var id = global_counter++;
          all_active_connections[id] = websocket;
          websocket.id = id; 
      }).on('message', function (data) {
          if (data == 'broadcast me!')
          {
              for (conn in all_active_connections)
                 all_active_connections[conn].write(data);  // 這里用的write
          }       
      }
    }).on('close', function() {
        delete all_active_connections[websocket.id];
    });
  }).listen(8080);

今天關于websocket的知識就講解到這里,其實有很多資源可以去學習,socket.io也是一個很好的學習途徑,如果有空我會做一期node.js配合socket的文章。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 二進制數組(ArrayBuffer對象、TypedArray視圖和DataView視圖)是JavaScript操作...
    呼呼哥閱讀 21,377評論 2 12
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,991評論 19 139
  • 壹:讀書無用論 我們很容易相信自己聽到看到的就是真實的,比如現在中國有大批的大學畢業生出來找工作好難,在大學也盛傳...
    夜語山林閱讀 639評論 1 1
  • 依稀記得印度電影最著名的洗腦音樂——大篷車里面的“阿~巴~拉~~古”。這一次又有一段旋律在腦子里揮之不去了,那充滿...
    清靈_簡書閱讀 649評論 0 51
  • 簡書,挺簡潔的。可惜現在的我已經不喜歡寫文章了。
    amoore閱讀 120評論 0 1