服务器推送

一、引入

随着网络应用的发展,越来越多的信息需要进行交互。在一些实时性要求较高的应用中,比如电商的商品余量信息,需要不断地发起网络请求,进行验证更新,以往的Http一次请求,一次响应的状态已经渐渐不能满足需求,这时服务端信息推送,就显得十分重要了。

二、以往的解决方案

在服务端推送技术没有出现之前,人们为了实现软件产品功能,使用了轮询的技术进行需求实现。说的通俗一点,就是写一个死循环,不断发起网络连接请求,不断去验证数据是否有变动。这对于客户端来说,不断发起网络请求也许还可以保证系统正常运行,但是对于服务端来说的话,就会产生大量的网络请求,导致服务器崩溃。我们称这种疯狂发起请求的数据请求方式为短轮询

为了缓解这个问题,我们推出了长轮询的概念。即在服务端收到请求后,不立刻返回信息,而是继续等待,如果在等待过程中,出现了数据的变动,就向前端返回数据。否则就一直等待,直到连接超时,然后客户端再次发起网络请求。这样虽然解决了网络的压力,但是在应对大量用户访问时,服务端处于等待过程中,需要开启大量的线程,大量线程的等待也是一个难以处理的问题。

让客户端不断发起请求,这种方式从逻辑上,就会产生大量的无意义连接数据,因为客户端根本不知道,服务端的数据什么时候变化,所以只好自已不断的去尝试了。要想彻底解决这个问题,就要使用服务端推送技术,在必要的时候,在进行网络通信,减小不必要的资源浪费。

三、网络推送技术

1.SSE

首先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事件触发条件:

  1. 当前端调用close方法,
  2. 关闭页面后再次进行推送,推送失败

【数据传输】:

通过抓包发现,这是个get请求。它的返回体,包含了所有推送的内容。在SSE推送中,连接始终保持数据传输状态,没有产生额外的数据请求。在 SSE 的草案中提到,“text/event-stream” 的 MIME 类型传输应当在静置 15 秒后自动断开。但实测(仅用了 Chrome)后发现,即使静置时间超过 15 秒,浏览器与客户端均不会断开连接。查阅了不少文章,均建议维护一套发送 \n\n 的心跳机制。个人认为此举有助于提高客户端程序的健壮性,但不是必须的。

2.Websocket

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

你可能感兴趣的:(服务器推送)