美文网首页
Html5 websocket浅析

Html5 websocket浅析

作者: 我是上帝可爱多 | 来源:发表于2017-08-03 22:29 被阅读32次

    初次接触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的文章。

    相关文章

      网友评论

          本文标题:Html5 websocket浅析

          本文链接:https://www.haomeiwen.com/subject/fknklxtx.html