您的位置:首页 > Web前端 > Webpack

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

2017-04-20 09:11 537 查看
本文包含如下内容:

webpack-hot-middleware

EventSource

CORS

nginx配置

browser-sync

WebSocket

支持性

Frame

建立连接

服务端实现

发送和监听消息

关闭连接

拥有的属性

文件上传

心跳连接

Socket.IO

小结

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

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

刚好,最近解决
webpack-hot-middleware
热更新延迟问题的过程中,我深入接触了EventSource技术。遂本文由此开篇,进一步讲解
webpack-hot-middleware
browser-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对象非常简单。
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
的响应头。
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主动向客户端发送当前时间戳,为了接受这个信息,客户端需要监听服务器。如下:
es.onmessage = function(e){
console.log(e.data); // 打印服务器推送的信息
}


如下是消息推送的过程:


response size不断增加


接收消息

你以为es只能监听message事件吗?并不是,message只是缺省的事件类型。实际上,它可以监听任何指定类型的事件。
es.addEventListener("####", function(e) {// 事件类型可以随你定义
console.log('####:', e.data);
},false);


服务器发送不同类型的事件时,需要指定event字段。
res.write('event: ####\n');
res.write('data: 这是一个自定义的####类型事件\n');
res.write('data: 多个data字段将被解析成一个字段\n\n');


如下所示:


####消息

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

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


混合消息

除此之外,es对象还拥有另外3个方法: 
onopen()
onerror()
close()
,请参考如下实现。
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.origin
 和 
Access-Control-Allow-Credentials:true
,并且创建es对象时,需要明确指定是否发送凭证。如下:
var es = new EventSource('/message', {
withCredentials: true
}); // 创建时指定配置才是有效的
es.withCredentials = true; // 与ajax不同,这样设置是无效的


以下是主流浏览器对EventSource的CORS的支持:
FirefoxOperaChromeSafariiOSAndroid
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即可。配置如下所示:
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
可以结合
Gulp
Grunt
一起使用,其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/EdgeFirefoxChromeSafariOpera
10+11+16+7+12.1+
如下是mobile端兼容性:
iOS SafariAndroidAndroid ChromeAndroid UCQQ BrowserOpera Mini
7.1+4.4+57+11.4+1.2+-


Frame

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

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


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

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

//若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对象十分简单。如下:
let ws = new WebSocket('ws://127.0.0.1:10103/');// 本地使用10103端口进行测试


新建的WebSocket对象如下所示:


Websocket对象

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

第一步,客户端请求。


Websocket Request

这是一个GET请求,主要字段如下:
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,还能传入一个可选的协议名称字符串或数组。
ws = new WebSocket('ws://127.0.0.1:10103/', ['abc','son_protocols']);


服务端实现

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

先做一些准备。ws是一个nodejs版的WebSocketServer实现。使用 
npm install ws
 即可安装。
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
为验权的函数。
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状态码的错误信息。
// 若verify定义如下
function verify(info, cb){
//一旦拥有第二个形参,如果不调用,默认将通过验权
cb(false, 401, '权限不够');// 此时表示验权失败,HTTP状态码为401,错误信息为"权限不够"
return true;// 一旦拥有第二个形参,响应就被cb接管了,返回什么值都不会影响前面的处理结果
}


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


发送和监听消息

接下来,我们来实现消息收发。如下是客户端发送消息。
ws.onopen = function(e){
// 可发送字符串,ArrayBuffer 或者 Blob数据
ws.send('client ready!);
};


客户端监听信息。
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下.
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连接未初始化00
OPEN连接已就绪11
CLOSING连接正在关闭-2
CLOSED连接已关闭23
另外,除了两种都有的url属性外,WebSocket对象还拥有更多的属性。
属性描述
binaryType被传输二进制内容的类型,有blob,arraybuffer两种
bufferedAmount待传输的数据的长度
extensions表示服务器选用的扩展
protocol指的是构造器第二个参数传入的子协议名称


文件上传

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

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

1) html。
<input type="file" id="file"/>


2) js。
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)。
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。
wsServer.ping();


同时,需要监听客户端响应的pong Frame.
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/

参考文章
RFC
6455 - The WebSocket Protocol
用node.js实现HTML5原生的comet(长连接)
使用服务器发送事件
- Server-sent events
HTML5
文件域+FileReader 分段读取文件并上传(七)-WebSocket
nodejs实现Websocket的数据接收发送
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: