随着网络应用的发展,越来越多的信息需要进行交互。在一些实时性要求较高的应用中,比如电商的商品余量信息,需要不断地发起网络请求,进行验证更新,以往的Http一次请求,一次响应的状态已经渐渐不能满足需求,这时服务端信息推送,就显得十分重要了。
在服务端推送技术没有出现之前,人们为了实现软件产品功能,使用了轮询的技术进行需求实现。说的通俗一点,就是写一个死循环,不断发起网络连接请求,不断去验证数据是否有变动。这对于客户端来说,不断发起网络请求也许还可以保证系统正常运行,但是对于服务端来说的话,就会产生大量的网络请求,导致服务器崩溃。我们称这种疯狂发起请求的数据请求方式为短轮询。
为了缓解这个问题,我们推出了长轮询的概念。即在服务端收到请求后,不立刻返回信息,而是继续等待,如果在等待过程中,出现了数据的变动,就向前端返回数据。否则就一直等待,直到连接超时,然后客户端再次发起网络请求。这样虽然解决了网络的压力,但是在应对大量用户访问时,服务端处于等待过程中,需要开启大量的线程,大量线程的等待也是一个难以处理的问题。
让客户端不断发起请求,这种方式从逻辑上,就会产生大量的无意义连接数据,因为客户端根本不知道,服务端的数据什么时候变化,所以只好自已不断的去尝试了。要想彻底解决这个问题,就要使用服务端推送技术,在必要的时候,在进行网络通信,减小不必要的资源浪费。
首先SSE 是相对轻量级的协议,基于http协议的推送技术,它的主要原理就是设置返回数据http-Header:"Content-Type": "text/event-stream"
,将返回结果,采用事件流的形式,逐步推送给客户端。这样客户端就不着急断开连接,而是等待下一次的推送。这是一个单向推送消息的模式,就是服务端不断推送数据到客户端的过程。
//初始化SSE
var url = "http://localhost:8083/sse";
var source = new EventSource(url);
//开启时调用
source.onopen = (event) => {
console.log("开启SSE");
}
//监听事件
source.addEventListener('connecttime', function(event) {
var data = event.data;
document.body.innerHTML += `${data}`;
}, false);
//发生异常时调用
source.onerror = (event) => {}
console.log(event);
}
setTimeout(()=>{
source.close()
},6000)
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive"
});
res.write('retry: 10000\n'); // 设置重连时间
res.write("event: connecttime\n");//设置消息类型
res.write("data: " + (new Date()) + "\n\n");//设置消息内容
interval = setInterval(function() {
res.write("event: connecttime\n");
res.write("data: " + (new Date()) + "\n\n");
}, 500);
req.connection.addListener("close", function() {
clearInterval(interval);
}, false);
req.connection.addListener("close", function() {
clearInterval(interval);
}, false);
【注意】:
event
为消息的事件类型。客户端在 EventSource
中可以通过 addEventListener
收听相关的消息。该字段可省略,省略后客户端触发 message
事件。
id
为事件 ID。作为客户端内部的“最后一个事件 ID”的属性值,用于重连,不可省略。
data
为消息的数据字段,简单说就是客户端监听时间后,通过e.data
拿到的数据。
retry
为重连时间,可省略该参数。
每条信息结束的 \n\n
,不可省略。
【原理】:
其实SSE推送的本质还是一个get请求,只不过后台通过设置属性,明确告诉前端,我返回的内容是一个事件推流的形式,前端通过EventSource
对象,逐步获取并解析推流的内容(\n\n
作为消息的分割),然后去触发对应的函数。
【中断检测】:
当客户端发现与服务器断开后,客户端会自动尝试再次与服务器进行连接,然后继续获取推流。即使后端使用了断开的连接的方法,客户端依然会进行重连尝试。只有客户端使用了close()方法后,才会中断重连尝试。这要求我们在服务器端,监控close事件,关闭对应的事件响应,防止触发异常。
close事件触发条件:
【数据传输】:
通过抓包发现,这是个get请求。它的返回体,包含了所有推送的内容。在SSE推送中,连接始终保持数据传输状态,没有产生额外的数据请求。在 SSE 的草案中提到,“text/event-stream” 的 MIME 类型传输应当在静置 15 秒后自动断开。但实测(仅用了 Chrome)后发现,即使静置时间超过 15 秒,浏览器与客户端均不会断开连接。查阅了不少文章,均建议维护一套发送 \n\n
的心跳机制。个人认为此举有助于提高客户端程序的健壮性,但不是必须的。
websocket是一个比较复杂的协议,它是一个新的协议,不是基于http,在老旧项目中实现 WebSocket,是需要另起一个服务,相对来开发成本会更大一些。websocket可以进行双向通信,即客户端和服务器之间可以相互发送消息。
var ws = new WebSocket("ws://localhost:5566/lokkol");
ws.onopen = function() {
// Web Socket 已连接上,使用 send() 方法发送数据
ws.send("发送数据");
};
ws.onmessage = function(evt) {
var received_msg = evt.data;
alert("数据已接收...");
};
ws.onclose = function() {
// 关闭 websocket
alert("连接已关闭...");
};
const WebSocket = require('ws');
const wss = new WebSocket.Server({
port: 567
});
wss.onupgrade = function() {
console.log(arguments);
let obj = req.obj;
}
wss.on('connection', function connection(ws, req) {
ws.onupgrade = function() {
console.log(arguments);
}
ws.on('message', function incoming(message) {
var obj = JSON.parse(message);
if (obj.type == "login") {
login(obj.data, ws);
}
if (obj.type == "signout") {
signout(ws);
}
});
ws.on('close', function close() {
global.onlineList.delete(ws.onLineName);
})
});
一个socket请求,其实最初还是一个http请求,只是里面的请求头中包含了一些特殊的属性,其中包含:
Upgrade: websocket
Connection: Upgrade
通过这个属相,服务端就知道这个网络请求是要将http请求升级为websocket。
服务端通过返回一下内容,表示接受了网络的升级:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat