webpack与browser-sync热更新原理深度讲解

目录
  1. webpack-hot-middleware
  2. EventSource
    1. CORS
    2. nginx配置
  3. browser-sync
  4. WebSocket
    1. 支持性
    2. Frame
    3. 建立连接
    4. 服务端实现
    5. 发送和监听消息
    6. 关闭连接
    7. 拥有的属性
    8. 文件上传
    9. 心跳连接
    10. Socket.IO
  5. 小结

本文首发于CSDN网站,下面的版本又经过进一步的修订。

开发环境页面热更新早已是主流,我们不光要吃着火锅唱着歌,享受热更新高效率的快感,更要深入下去探求其原理。

要知道,触类则旁通,常见的需求如赛事网页推送比赛结果、网页实时展示投票或点赞数据、在线评论或弹幕、在线聊天室等,都需要借助热更新功能,才能达到实时的端对端的极致体验。

刚好,最近解决webpack-hot-middleware热更新延迟问题的过程中,我深入接触了EventSource技术。遂本文由此开篇,进一步讲解webpack-hot-middlewarebrowser-sync背后的技术。

webpack-hot-middleware

webpack-hot-middleware中间件是webpack的一个plugin,通常结合webpack-dev-middleware一起使用。借助它可以实现浏览器的无刷新更新(热更新),即webpack里的HMR(Hot Module Replacement)。如何配置请参考 webpack-hot-middleware,如何理解其相关插件请参考 手把手深入理解 webpack dev middleware 原理與相關 plugins

webpack加入webpack-hot-middleware后,内存中的页面将包含HMR相关js,加载页面后,Network栏可以看到如下请求:

__webpack_hmr

__webpack_hmr是一个type为EventSource的请求, 从Time栏可以看出:默认情况下,服务器每十秒推送一条信息到浏览器。

hmr每10秒推送一条信息

如果此时关闭开发服务器,浏览器由于重连机制,将持续抛出类似GET http://www.test.com/__webpack_hmr 502 (Bad Gateway) 这样的错误。重新启动开发服务器后,重连将会成功,此时便会刷新页面。

以上这些便是我们使用时感受到的最初的印象。当然,停留在使用层面不是我们的目标,接下来我们将跳出该中间件,讲解其所使用到的EventSource技术。

EventSource

EventSource 不是一个新鲜的技术,它早就随着H5规范提出了,正式一点应该叫Server-sent events,即SSE

鉴于传统的通过ajax轮训获取服务器信息的技术方案已经过时,我们迫切需要一个高效的节省资源的方式去获取服务器信息,一旦服务器资源有更新,能够及时地通知到客户端,从而实时地反馈到用户界面上。EventSource就是这样的技术,它本质上还是HTTP,通过response流实时推送服务器信息到客户端。

新建一个EventSource对象非常简单。

1
const es = new EventSource('/message');// /message是服务端支持EventSource的接口

新创建的EventSource对象拥有如下属性:

属性 描述
url(只读) es对象请求的服务器url
readyState(只读) es对象的状态,初始为0,包含CONNECTING (0),OPEN (1),CLOSED (2)三种状态
withCredentials 是否允许带凭证等,默认为false,即不支持发送cookie

服务端实现/message接口,需要返回类型为 text/event-stream的响应头。

1
2
3
4
5
6
7
8
9
10
11
12
13
var http = require('http');
http.createServer(function(req,res){
if(req.url === '/message'){
res.writeHead(200,{
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
setInterval(function(){
res.write('data: ' + +new Date() + '\n\n');
}, 1000);
}
}).listen(8888);

我们注意到,为了避免缓存,Cache-Control 特别设置成了 no-cache,为了能够发送多个response, Connection被设置成了keep-alive.。发送数据时,请务必保证服务器推送的数据以 data:开始,以\n\n结束,否则推送将会失败(原因就不说了,这是约定的)。

以上,服务器每隔1s主动向客户端发送当前时间戳,为了接受这个信息,客户端需要监听服务器。如下:

1
2
3
es.onmessage = function(e){
console.log(e.data); // 打印服务器推送的信息
}

如下是消息推送的过程:

response size不断增加

接收消息

你以为es只能监听message事件吗?并不是,message只是缺省的事件类型。实际上,它可以监听任何指定类型的事件。

1
2
3
es.addEventListener("####", function(e) {// 事件类型可以随你定义
console.log('####:', e.data);
},false);

服务器发送不同类型的事件时,需要指定event字段。

1
2
3
res.write('event: ####\n');
res.write('data: 这是一个自定义的####类型事件\n');
res.write('data: 多个data字段将被解析成一个字段\n\n');

如下所示:

####消息

可以看到,服务端指定event事件名为”####”后,客户端触发了对应的事件回调,同时服务端设置的多个data字段,客户端使用换行符连接成了一个字符串。

不仅如此,事件流中还可以混合多种事件,请看我们是怎么收到消息的,如下:

混合消息

除此之外,es对象还拥有另外3个方法: onopen()onerror()close(),请参考如下实现。

1
2
3
4
5
6
7
es.onopen = function(e){// 链接打开时的回调
console.log('当前状态readyState:', es.readyState);// open时readyState===1
}
es.onerror = function(e){// 出错时的回调(网络问题,或者服务下线等都有可能导致出错)
console.log(es.readyState);// 出错时readyState===0
es.close();// 出错时,chrome浏览器会每隔3秒向服务器重发原请求,直到成功. 因此出错时,可主动断开原连接.
}

使用EventSource技术实时更新网页信息十分高效。实际使用中,我们几乎不用担心兼容性问题,主流浏览器都了支持EventSource,当然,除了掉队的IE系。对于不支持的浏览器,其PolyFill方案请参考HTML5 Cross Browser Polyfills

CORS

另外,如果需要支持跨域调用,请设置响应头Access-Control-Allow-Origin': '*'

如需支持发送cookie,请设置响应头Access-Control-Allow-Origin': req.headers.originAccess-Control-Allow-Credentials:true,并且创建es对象时,需要明确指定是否发送凭证。如下:

1
2
3
4
var es = new EventSource('/message', {
withCredentials: true
}); // 创建时指定配置才是有效的
es.withCredentials = true; // 与ajax不同,这样设置是无效的

以下是主流浏览器对EventSource的CORS的支持:

Firefox Opera Chrome Safari iOS Android
10+ 12+ 26+ 7.0+ 7.0+ 4.4+

nginx配置

既然说到了EventSource,便有必要谈谈遇到的坑,接下来,就说说我遇到的webpack热更新延迟问题。

如我们所知,webpack借助webpack-hot-middleware插件,实现了网页热更新机制,正常情况下,浏览器打开 http://localhost:8080 这样的网页即可开始调试。然而实际开发中,由于远程服务器需要种cookie登录态到特定的域名上等原因,因此本地往往会用nginx做一层反向代理。即把 http://www.test.com 的请求转发到 http://localhost:8080 上(配置过程这里不详述,具体请参考Ajax知识体系大梳理-ajax调试技巧)。转发过后,发现热更新便延迟了。

原因是nginx默认开启的buffer机制缓存了服务器推送的片段信息,缓存达到一定的量才会返回响应内容。只要关闭proxy_buffering即可。配置如下所示:

1
2
3
4
5
6
7
8
server {
listen 80;
server_name www.test.company.com;
location / {
proxy_pass http://localhost:8080;
proxy_buffering off;
}
}

至此,EventSource部分便告一段落。学习讲究由浅入深,循序渐进。后面我将重点讲解的browser-sync热更新机制,请耐心细读。

browser-sync

开发中使用browser-sync插件调试,一个网页里的所有交互动作(包括滚动,输入,点击等等),可以实时地同步到其他所有打开该网页的设备,能够节省大量的手工操作时间,从而带来流畅的开发调试体验。目前browser-sync可以结合GulpGrunt一起使用,其API请参考:Browsersync API

通过上面的了解,我们知道EventSouce的使用是比较便捷的,那为什么browser-sync不使用EventSource技术进行代码推送呢?这是因为browser-sync插件共做了两件事:

  • 开发更新了一段新的逻辑,服务器实时推送代码改动信息。数据流:服务器 —> 浏览器,使用EventSource技术同样能够实现。
  • 用户操作网页,滚动、输入或点击等,操作信息实时发送给服务器,然后再由服务器将操作同步给其他已打开的网页。数据流:浏览器 —> 服务器 —> 浏览器,该部分功能EventSource技术已无能为力。

以上,browser-sync使用WebSocket技术达到实时推送代码改动和用户操作两个目的。至于它是如何计算推送内容,根据不同推送内容采取何种响应策略,不在本次讨论范围之内。下面我们将讲解其核心的WebSocket技术。

WebSocket

WebSocket是基于TCP的全双工通讯的协议,它与EventSource有着本质上的不同.(前者基于TCP,后者依然基于HTTP) 该协议于2011年被IETF定为标准RFC6455,后被RFC7936补充. WebSocket api也被W3C定为标准。

WebSocket使用和HTTP相同的TCP端口,默认为80, 统一资源标志符为ws,运行在TLS之上时,默认使用443,统一资源标志符为wss。它通过101 switch protocol进行一次TCP握手,即从HTTP协议切换成WebSocket通信协议。

相对于HTTP协议,WebSocket拥有如下优点:

  • 全双工,实时性更强。
  • 相对于http携带完整的头部,WebSocket请求头部明显减少。
  • 保持连接状态,不用再验权了。
  • 二进制支持更强,Websocket定义了二进制帧,处理更轻松。
  • Websocket协议支持扩展,可以自定义的子协议,如 permessage-deflate 扩展。

支持性

优秀技术的落地,调研兼容性是必不可少的环节。所幸的是,现代浏览器对WebSocket的支持比较友好,如下是PC端兼容性:

IE/Edge Firefox Chrome Safari Opera
10+ 11+ 16+ 7+ 12.1+

如下是mobile端兼容性:

iOS Safari Android Android Chrome Android UC QQ Browser Opera Mini
7.1+ 4.4+ 57+ 11.4+ 1.2+ -

Frame

根据RFC6455文档,WebSocket协议基于Frame而非Stream(EventSource是基于Stream的)。因此其传输的数据都是Frame(帧)。想要了解数据的往返,弄懂协议处理过程,Frame的解读是必不可少。如下便是Frame的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued,if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key,if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+

第一个字节包含FIN、RSV、Opcode。

  • FIN:size为1bit,标示是否最后一帧。%x0表示还有后续帧,%x1表示这是最后一帧。

  • RSV1、2、3,每个size都是1bit,默认值都是0,如果没有定义非零值的含义,却出现了非零值,则WebSocket链接将失败。

  • Opcode,size为4bits,表示『payload data』的类型。如果收到未知的opcode,连接将会断开。已定义的opcode值如下:

    1
    2
    3
    4
    5
    6
    7
    8
    %x0: 代表连续的帧
    %x1: 文本帧
    %x2: 二进制帧
    %x3~7: 预留的非控制帧
    %x8: 关闭握手帧
    %x9: ping帧,后续心跳连接会讲到
    %xA: pong帧,后续心跳连接会讲到
    %xB~F: 预留的非控制帧

第二个字节包含Mask、Payload len。

  • Mask:size为1bit,标示『payload data』是否添加掩码。所有从客户端发送到服务端的帧都会被置为1,如果置1,Masking-key便会赋值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //若server是一个WebSocket服务端实例
    //监听客户端消息
    server.on('message', function(msg, flags) {
    console.log('client say: %s', msg);
    console.log('mask value:', flags.masked);// true,进一步佐证了客户端发送到服务端的Mask帧都会被置为1
    });
    //监听客户端pong帧响应
    server.on('pong', function(msg, flags) {
    console.log('pong data: %s', msg);
    console.log('mask value:', flags.masked);// true,进一步佐证了客户端发送到服务端的Mask帧都会被置为1
    });
  • Payload len:size为7bits,即使是当做无符号整型也只能表示0~127的值,所以它不能表示更大的值,因此规定”Payload data”长度小于或等于125的时候才用来描述数据长度。如果Payload len==126,则使用随后的2bytes(16bits)来存储数据长度。如果Payload len==127,则使用随后的8bytes(64bits)来存储数据长度。

以上,扩展的Payload len可能占据第三至第四个或第三至第十个字节。紧随其后的是”Mask-key”。

  • Mask-key:size为0或4bytes(32bits),默认为0,与前面Mask呼应,从客户端发送到服务端的帧都包含4bytes(32bits)的掩码,一旦掩码被设置,所有接收到的”payload data”都必须与该值以一种算法做异或运算来获取真实值。
  • Payload data:size为”Extension data” 和 “Application data” 的总和,一般”Extension data”数据为空。
  • Extension data:默认为0,如果扩展被定义,扩展必须指定”Extension data”的长度。
  • Application data:占据”Extension data”之后剩余帧的空间。

关于Frame的更多理论介绍不妨读读 学习WebSocket协议—从顶层到底层的实现原理(修订版)

关于Frame的数据帧解析不妨读读 WebSocket(贰) 解析数据帧 及其后续文章。

建立连接

了解了Frame的数据结构后,我们来实际练习下。浏览器上,新建一个ws对象十分简单。如下:

1
let ws = new WebSocket('ws://127.0.0.1:10103/');// 本地使用10103端口进行测试

新建的WebSocket对象如下所示:

Websocket对象

这中间包含了一次Websocket握手的过程,我们分两步来理解。

第一步,客户端请求。

Websocket Request

这是一个GET请求,主要字段如下:

1
2
3
4
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key:61x6lFN92sJHgzXzCHfBJQ==
Sec-WebSocket-Version:13

Connection字段指定为Upgrade,表示客户端希望连接升级。

Upgrade字段设置为websocket,表示希望升级至Websocket协议。

Sec-WebSocket-Key字段是随机字符串,服务器根据它来构造一个SHA-1的信息摘要。

Sec-WebSocket-Version表示支持的Websocket版本。RFC6455要求使用的版本是13。

甚至我们可以从请求截图里看出,Origin是file://,而Host是127.0.0.1:10103,明显不是同一个域下,但依然可以请求成功,说明Websocket协议是不受同源策略限制的(同源策略限制的是http协议)。

第二步,服务端响应。

Websocket Response

Status Code: 101 Switching Protocols 表示Websocket协议通过101状态码进行握手。

Sec-WebSocket-Accept字段是由Sec-WebSocket-Key字段加上特定字符串”258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,计算SHA-1摘要,然后再base64编码之后生成的. 该操作可避免普通http请求,被误认为Websocket协议。

Sec-WebSocket-Extensions字段表示服务端对Websocket协议的扩展。

以上,WebSocket构造器不止可以传入url,还能传入一个可选的协议名称字符串或数组。

1
ws = new WebSocket('ws://127.0.0.1:10103/', ['abc','son_protocols']);

服务端实现

等等,我们慢一点,上面好像漏掉了一步,似乎没有提到服务端是怎么实现的。请继续往下看:

先做一些准备。ws是一个nodejs版的WebSocketServer实现。使用 npm install ws 即可安装。

1
2
3
4
5
6
7
8
var WebSocketServer = require('ws').Server,
server = new WebSocketServer({port: 10103});
server.on('connection', function(s) {
s.on('message', function(msg) { //监听客户端消息
console.log('client say: %s', msg);
});
s.send('server ready!');// 连接建立好后,向客户端发送一条消息
});

以上,new WebSocketServer()创建服务器时如需权限验证,请指定verifyClient为验权的函数。

1
2
3
4
5
6
7
8
9
server = new WebSocketServer({
port: 10103,
verifyClient: verify
});
function verify(info){
console.log(Object.keys(info));// [ 'origin', 'secure', 'req' ]
console.log(info.orgin);// "file://"
return true;// 返回true时表示验权通过,否则客户端将抛出"HTTP Authentication failed"错误
}

以上,verifyClient指定的函数只有一个形参,若为它显式指定两个形参,那么第一个参数同上info,第二个参数将是一个cb回调函数。该函数用于显式指定拒绝时的HTTP状态码等,它默认拥有3个形参,依次为:

  • result,布尔值类型,表示是否通过权限验证。
  • code,数值类型,若result值为false时,表示HTTP的错误状态码。
  • name,字符串类型,若result值为false时,表示HTTP状态码的错误信息。
1
2
3
4
5
6
// 若verify定义如下
function verify(info, cb){
//一旦拥有第二个形参,如果不调用,默认将通过验权
cb(false, 401, '权限不够');// 此时表示验权失败,HTTP状态码为401,错误信息为"权限不够"
return true;// 一旦拥有第二个形参,响应就被cb接管了,返回什么值都不会影响前面的处理结果
}

除了portverifyClient设置外,其它设置项及更多API,请参考文档 ws-doc

发送和监听消息

接下来,我们来实现消息收发。如下是客户端发送消息。

1
2
3
4
ws.onopen = function(e){
// 可发送字符串,ArrayBuffer 或者 Blob数据
ws.send('client ready!);
};

客户端监听信息。

1
2
3
ws.onmessage = function(e){
console.log('server say:', e.data);
};

如下是浏览器的运行截图。

message

消息的内容都在Frames栏,第一条彩色背景的信息是客户端发送的,第二条是服务端发送的。两条消息的长度都是13。

如下是Timing栏,不止是WebSocket,包括EventSource,都有这样的黄色高亮警告。

Websocket Request

该警告说明:请求还没完成。实际上,直到一方连接close掉,请求才会完成。

关闭连接

说到close,ws的close方法比es的略复杂。

语法:close(short code,string reason);

close默认可传入两个参数。code是数字,表示关闭连接的状态号,默认是1000,即正常关闭。(code取值范围从0到4999,其中有些是保留状态号,正常关闭时只能指定为1000或者3000~4999之间的值,具体请参考CloseEvent - Web APIs)。reason是UTF-8文本,表示关闭的原因(文本长度需小于或等于123字节)。

由于code 和 reason都有限制,因此该方法可能抛出异常,建议catch下.

1
2
3
4
5
try{
ws.close(1001, 'CLOSE_GOING_AWAY');
}catch(e){
console.log(e);
}

ws对象还拥有onclose和onerror监听器,分别监听关闭和错误事件。(注:EventSource没有onclose监听)

拥有的属性

ws的readyState属性拥有4个值,比es的readyState的多一个CLOSING的状态。

常量 描述 EventSource(值) WebSocket(值)
CONNECTING 连接未初始化 0 0
OPEN 连接已就绪 1 1
CLOSING 连接正在关闭 - 2
CLOSED 连接已关闭 2 3

另外,除了两种都有的url属性外,WebSocket对象还拥有更多的属性。

属性 描述
binaryType 被传输二进制内容的类型,有blob,arraybuffer两种
bufferedAmount 待传输的数据的长度
extensions 表示服务器选用的扩展
protocol 指的是构造器第二个参数传入的子协议名称

文件上传

以前一直是使用ajax做文件上传,实际上,Websocket上传文件也是一把好刀. 其send方法可以发送String,ArrayBuffer,Blob共三种数据类型,发送二进制文件完全不在话下。

由于各个浏览器对Websocket单次发送的数据有限制,所以我们需要将待上传文件切成片段去发送。如下是实现。

1) html。

1
<input type="file" id="file"/>

2) js。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const ws = new WebSocket('ws://127.0.0.1:10103/');// 连接服务器
const fileSelect = document.getElementById('file');
const size = 1024 * 128;// 分段发送的文件大小(字节)
let curSize, total, file, fileReader;
fileSelect.onchange = function(){
file = this.files[0];// 选中的待上传文件
curSize = 0;// 当前已发送的文件大小
total = file.size;// 文件大小
ws.send(file.name);// 先发送待上传文件的名称
fileReader = new FileReader();// 准备读取文件
fileReader.onload = loadAndSend;
readFragment();// 读取文件片段
};
function loadAndSend(){
if(ws.bufferedAmount > size * 5){// 若发送队列中的数据太多,先等一等
setTimeout(loadAndSend,4);
return;
}
ws.send(fileReader.result);// 发送本次读取的片段内容
curSize += size;// 更新已发送文件大小
curSize < total ? readFragment() : console.log('upload successed!');// 下一步操作
}
function readFragment(){
const blob = file.slice(curSize, curSize + size);// 获取文件指定片段
fileReader.readAsArrayBuffer(blob);// 读取文件为ArrayBuffer对象
}

3) server(node)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var WebSocketServer = require('ws').Server,
server = new WebSocketServer({port: 10103}),// 启动服务器
fs = require('fs');
server.on('connection', function(wsServer){
var fileName, i = 0;// 变量定义不可放在全局,因每个连接都不一样,这里才是私有作用域
server.on('message', function(data, flags){// 监听客户端消息
if(flags.binary){// 判断是否二进制数据
var method = i++ ? 'appendFileSync' : 'writeFileSync';
// 当前目录下写入或者追加写入文件(建议加上try语句捕获可能的错误)
fs[method]('./' + fileName, data,'utf-8');
}else{// 非二进制数据则认为是文件名称
fileName = data;
}
});
wsServer.send('server ready!');// 告知客户端服务器已就绪
});

运行效果如下:

Websocket upload

上述测试代码中没有过多涉及服务器的存储过程。通常,服务器也会有缓存区上限,如果客户端单次发送的数据量超过服务端缓存区上限,那么服务端也需要多次读取。

心跳连接

生产环境下上传一个文件远比本地测试来得复杂。实际上,从客户端到服务端,中间存在着大量的网络链路,如路由器,防火墙等等。一份文件的上传要经过中间的层层路由转发,过滤。这些中间链路可能会认为一段时间没有数据发送,就自发切断两端的连接。这个时候,由于TCP并不定时检测连接是否中断,而通信的双方又相互没有数据发送,客户端和服务端依然会一厢情愿的信任之前的连接,长此以往,将使得大量的服务端资源被WebSocket连接占用。

正常情况下,TCP的四次挥手完全可以通知两端去释放连接。但是上述这种普遍存在的异常场景,将使得连接的释放成为梦幻。

为此,早在websocket协议实现时,设计者们便提供了一种 Ping/Pong Frame的心跳机制。一端发送Ping Frame,另一端以 Pong Frame响应。这种Frame是一种特殊的数据包,它只包含一些元数据,能够在不影响原通信的情况下维持住连接。

根据规范RFC 6455,Ping Frame包含一个值为9的opcode,它可能携带数据。收到Ping Frame后,Pong Frame必须被作为响应发出。Pong Frame包含一个值为10的opcode,它将包含与Ping Frame中相同的数据。

借助ws包,服务端可以这么来发送Ping Frame。

1
wsServer.ping();

同时,需要监听客户端响应的pong Frame.

1
2
3
4
wsServer.on('pong', function(data, flags) {
console.log(data);// ""
console.log(flags);// { masked: true,binary: true }
});

以上,由于Ping Frame 不带数据,因此作为响应的Pong Frame的data值为空串。遗憾的是,目前浏览器只能被动发送Pong Frame作为响应(Sending websocket ping/pong frame from browser),无法通过JS API主动向服务端发送Ping Frame。因此对于web服务,可以采取服务端主动ping的方式,来保持住链接。实际应用中,服务端还需要设置心跳的周期,以保证心跳连接可以一直持续。同时,还应该有重发机制,若连续几次没有收到心跳连接的回复,则认为连接已经断开,此时便可以关闭Websocket连接了。

Socket.IO

WebSocket出世已久,很多优秀的大神基于此开发出了各式各样的库。其中Socket.IO是一个非常不错的开源WebSocke库,旨在抹平浏览器之间的兼容性问题。它基于Node.js,支持以下方式优雅降级:

  • Websocket
  • Adobe® Flash® Socket
  • AJAX long polling
  • AJAX multipart streaming
  • Forever Iframe
  • JSONP Polling

如何在项目中使用Socket.IO,请参考第一章 socket.io 简介及使用

小结

EventSource,本质依然是HTTP,它仅提供服务端到客户端的单向文本数据传输,不需要心跳连接,连接断开会持续触发重连。

WebSocket协议,基于TCP协议,它提供双向数据传输,支持二进制,需要心跳连接,连接断开不会重连。

EventSource更轻量和简单,WebSocket支持性更好(因其支持IE10+)。通常来说,使用EventSource能够完成的功能,使用WebSocket一样能够做到,反之却不行,使用时若遇到连接断开或抛错,请及时调用各自的close方法主动释放资源。


本问就讨论这么多内容,大家有什么问题或好的想法欢迎在下方参与留言和评论

本文作者: louis

本文链接: http://louiszhai.github.io/2017/04/19/hmr/

参考文章

Fork me on GitHub