Web Socket、Web Worker、Service Worker区别解析

3,516 阅读9分钟

1、Web Socket

1.1、简介

我们平时用的较多的是HTTP 协议,它的通信只能由客户端发起,是一种单向请求。如果服务器有连续的状态变化,客户端要获知就非常麻烦。

所以我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。而且轮询的效率低,非常浪费资源。为了解决这个问题,WebSocket 就被发明了。

WebSocket 也是一种网络通信协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

如下是HTTP和WebSocket与TCP的关系以及数据传输方式。

58a8a9a101fe5ed92ddb2d6aef8d9285.png

6ddefbb7ec4e23ac14a514c4062a6860.png

WebSocket的其他特点:

  1. 数据格式比较轻量,性能开销小,通信高效;
  2. 可以发送文本,也可以发送二进制数据;
  3. 没有同源限制,客户端可以与任意服务器通信;
  4. 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL,例如 ws://example.com:80/some/path

1.2、API

new WebSocket:WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。执行如下语句之后,客户端就会与服务器进行连接。

var ws = new WebSocket("wss://echo.websocket.org");

ws.readyState: 返回实例对象的当前状态,共有四种

  1. CONNECTING:值为0,表示正在连接。
  2. OPEN:值为1,表示连接成功,可以通信了。
  3. CLOSING:值为2,表示连接正在关闭。
  4. CLOSED:值为3,表示连接已经关闭,或者打开连接失败。 ws.onopen:连接成功后的回调函数 ws.send:向服务器发送数据(数据请求体)
ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");  	
};

ws.onmessage:收到服务器返回的数据的回调函数

ws.onclose:连接关闭后的回调函数。是一个事件监听器,这个事件监听器将在 WebSocket 连接的readyState变为 CLOSED时被调用,它接收一个名字为“onclose”的事件。

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);  // 处理返回的数据
  ws.close();  // 使用完成需要手动关闭
};
ws.onclose = function(evt) {
  console.log("Connection closed.");
};

ws.onerror:报错时的回调函数

ws.onerror = function(event) {
  // handle error event
};

1.3、实战

本机模拟启动一个websocketd服务,首先安装brew install websocketd。

启动WebSocket服务:websocketd --port=8080 node count.js

(function(){
    var counter = 0;
    var echo = function(){
        if (counter === 10){
            return;
        }
        setTimeout(function(){
            counter++;
            echo();
            process.stdout.write(counter.toString() + "\n");
        }, 1000);
    }
    echo();
})();

客户端接收数据

<div id="count"></div>
<script>
    var ws = new WebSocket('ws://' + (location.host ? location.host : 'localhost:8080') + '/');
    ws.onopen = function () {
        document.body.style.backgroundColor = '#FF689B';
    };
    ws.onmessage = function (event) {
        document.getElementById('count').textContent = event.data;
    };
    ws.onclose = function () {
        document.body.style.backgroundColor = null;
    };
</script>

websocketd官网:http://websocketd.com/

其它语言的case地址:github.com/joewalnes/w…

1.4、使用场景

社交订阅、体育实况更新、多媒体聊天、弹幕......

1.5、心跳及重连机制

在使用websocket的过程中,有时候会遇到网络断开的情况,但是在网络断开的时候服务器端并没有触发onclose的事件。这样会有:服务器会继续向客户端发送多余的链接,并且这些数据还会丢失。所以就需要一种机制来检测客户端和服务端是否处于正常的链接状态。因此就有了websocket的心跳了。还有心跳,说明还活着,没有心跳说明已经挂掉了。

心跳机制是什么?

心跳机制是每隔一段时间会向服务器发送一个数据包,告诉服务器自己还活着,同时客户端会确认服务器端是否还活着,如果还活着的话,就会回传一个数据包给客户端来确定服务器端也还活着,否则的话,有可能是网络断开连接了。需要重连~

<html>
<head>
  <meta charset="utf-8">
  <title>WebSocket Demo</title>
</head>
<body>
  <script type="text/javascript">
    var lockReconnect = false;//避免重复连接
    var wsUrl = "wss://echo.websocket.org";
    var ws;
    var tt;
	// 创建WebSocket的连接
    function createWebSocket() {
      try {
        ws = new WebSocket(wsUrl);
        init();
      } catch(e) {
        console.log('catch');
        reconnect(wsUrl);
      }
    }
	// 连接初始化
    function init() {
      ws.onopen = function () {
        //心跳检测重置
        heartCheck.start();
      };
      ws.onmessage = function (event) {
        //拿到任何消息都说明当前连接是正常的
        console.log('接收到消息');
        heartCheck.start();
      }
      ws.onclose = function () {
        console.log('链接关闭');
        reconnect(wsUrl);
      };
      ws.onerror = function() {
        console.log('发生异常了');
        reconnect(wsUrl);
      };
    }
	// 断开重连
    function reconnect(url) {
      if(lockReconnect) {
        return;
      };
      lockReconnect = true;
      //没连接上会一直重连,设置延迟避免请求过多
      tt && clearTimeout(tt);
      tt = setTimeout(function () {
        createWebSocket(url);
        lockReconnect = false;
      }, 4000);
    }
    //心跳检测
    var heartCheck = {
      timeout: 3000,
      timeoutObj: null,
      serverTimeoutObj: null,
      start: function(){
        console.log('start');
        var self = this;
        this.timeoutObj && clearTimeout(this.timeoutObj);
        this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
        this.timeoutObj = setTimeout(function(){
          //这里发送一个心跳,后端收到后,返回一个心跳消息,
          console.log('55555');
          ws.send("123456789");
          self.serverTimeoutObj = setTimeout(function() {
            console.log(111);
            console.log(ws);
            ws.close();
            // createWebSocket();
          }, self.timeout);
        }, this.timeout)
      }
    }
    createWebSocket(wsUrl);
  </script>
</body>
</html>

实现心跳检测的思路:

每隔一段固定的时间,向服务器端发送一个ping数据,如果在正常的情况下,服务器会返回一个pong给客户端,如果客户端通过

onmessage事件能监听到的话,说明请求正常,这里我们使用了一个定时器,每隔3秒的情况下,如果是网络断开的情况下,在指定的时间内服务器端并没有返回心跳响应消息,因此服务器端断开了,因此这个时候我们使用ws.close关闭连接,在一段时间后(在不同的浏览器下,时间是不一样的,firefox响应更快),

可以通过 onclose事件监听到。因此在onclose事件内,我们可以调用 reconnect事件进行重连操作。

2、Web Worker

2.1、简介

JS是单线程的,如果要处理一些复杂的逻辑计算,又不耽误主线程的执行顺序且要减少耗时,则可以考虑使用多线程。web worker的实现过程就是一种多线程的机制。

特点

  1. 允许主线程创建worker线程,将一些复杂的计算交给后者运行;
  2. 主线程运行的同时,worker线程在后台同步运行,两者互不干扰;
  3. worker线程一旦新建成功,就会始终运行,不会被主线程的活动(点击事件、表单提交)打断。

缺点

  1. worker线程比较耗费资源,不应过度使用,一旦使用完成应关闭。

2.2、API

主线程的主要API操作

new Worker:在创建线程的时候需要给实例化的Worker传入唯一个参数,指向一个js文件,调用这个构造函数之后,一个worker线程就被创建了

var worker = new Worker("worker.js")

// 主线程向 worker 线程主动推送数据,worker 线程可以使用 onmessage 来接收数据(这里的 onmessage 要和下面的 worker.onmessage 要区别理解,一个是 worker 线程的 onmessage,一个是主线程的 onmessage)
worker.postMessage(res)

// worker.onmessage: 主线程接收 worker 线程数据,并进行处理
worker.onmessage = event => {
   // console.log(event.data);   
}

// worker.onerror:异常处理
worker.onerror = event => {
   // console.log(event);   
}

// worker.terminate:在主线程关闭线程,与self.close() 二者取其一就可以
worker.terminate()

worker.js线程的主要API操作

// worker 线程接收主线程数据,并进行处理
onmessage = (event) => {
    console.log(event.data)
}

/**
 *  self.postMessage: 返回计算好的结果res
 *  self 是指向全局作用域的对象,只在 worker 线程内部使用,类似于 window
 */
self.postMessage(res)

// self.close: worker 线程关闭线程,与 worker.terminate() 二者取其一就可以
self.close()

注意事项

  1. 主线程和 worker 线程均可以使用 postMessage 发送数据和使用 onmessage 接收数据
  2. 若创建多个 worker 线程,不同的线程之间是同步执行的

2.3、实战

单线程处理“斐波那契数列”耗时10秒左右

function fb(n) {
    if (n <= 2) {
        return 1;
    } else {
        return fb(n - 1) + fb(n - 2);
    }
}
console.log('单线程 start');
console.time('time');
const res1 = fb(43);
const res2 = fb(43);
const res3 = fb(43);
console.timeEnd('time'); 		 // 耗时10秒左右

多线程处理“斐波那契数列”耗时3秒左右

const worker1 = new Worker('./worker1.js');
const worker2 = new Worker('./worker2.js');
const worker3 = new Worker('./worker3.js');
console.log('多线程 start');
console.time('time');
worker1.onmessage = event => {
    // console.log(event.data);   // 数据处理
};
worker2.onmessage = event => {
    // console.log(event.data);
};
worker3.onmessage = event => {
    // console.log(event.data);
    console.timeEnd('time');      // 耗时3秒左右
};

2.4、使用场景

计算量大的交互处理、预渲染、加密数据......

3、Service Worker

Service Worker是一种离线缓存技术,属于web worker的一种,是W3C在2014年5月提出的,其前身是Application Cache,因为Application Cache存在多种兼容问题,现在是已经废弃不用了。

所以现在使用service worker可以实现web APP的一些离线缓存。

特性

  1. 是浏览器在后台独立于网页运行的脚本;
  2. 可以拦截和处理网络请求,操作缓存;
  3. 不能直接访问/操作DOM;
  4. 需要时直接被唤醒,不需要时自动休眠;
  5. 一旦被安装则永久存活,除非手动卸载;
  6. 出于安全考虑,因为service worker很强大,可以改写响应和请求,所以必需使用HTTPS的环境来运行。

运行机制

image2021-9-24_15-37-33.png

生命周期

image2021-9-24_19-3-42.png

关键点:

  1. install安装过程会开辟一块缓存区域,来指定我们缓存的文件列表;
  2. 激活后开发者就可以控制界面,根据情况service worker被唤醒或自动停止运行,或显示被结束的进程。若某一步失败,则当前的service worker就会被废弃;
  3. 如果修改了service worker,则会重新进行注册、安装、激活的过程。等待上一个进程结束,才会启动这个新的进程,若想跳过等待,需要在install上面设置skipwaiting跳过等待。

注意事项

  1. service worker不支持xmlHttpRequest请求,支持Fetch请求;
  2. 使用service worker时避免http同时缓存service worker文件;
  3. service worker最大缓存时间为24小时。

4、总结

Web Socket

在客户端和服务端之间建立保持双向通信的连接。适用于需要保持长推送的情形,如聊天应用、社交订阅或运动直播等。使用send方法发送数据,onmessage方法接收数据。

Web Worker

多线程,允许复杂计算功能的脚本在后台运行而不会阻碍到其他脚本的运行。适用于处理器占用量大而又不阻碍的情形。通过postMessage方法进行数据传递。

Service Worker

处理网络请求的后台服务。适用于离线和后台同步数据或推送信息。通过postMessage方法交互。)

如上讲的三种技术都可以归属于“性能优化”的范畴,技术虽好,但是不要乱用,选择合适的技术在项目中应用才是最佳的选择。