首页
Preview

轮询、SSE和WebSocket的比较——如何选择合适的方式

建立一个实时的Web应用是有些挑战性的,我们需要考虑如何从服务器将数据发送到客户端。实现这种“主动性”的技术已经存在一段时间,一般限制在两种方法上:客户端拉取或服务器推送。

实现这些的几种方式有:

  • 长/短轮询(客户端拉取)
  • WebSockets(服务器推送)
  • 服务器发送事件(服务器推送)

客户端拉取 - 客户端定期请求更新

服务器推送 - 服务器主动向客户端推送更新(与客户端拉取相反)

下面我们来举个简单的例子来比较上述技术并选择合适的技术。

例子:

我们的例子非常简单。我们需要开发一个仪表盘Web应用,从像(GitHub/Twitter/..等)的网站流式传输活动列表。这个应用的目的是从之前列出的各种方法中选择正确的方法。

1. 使用轮询:

轮询是一种技术,客户端定期向服务器请求新数据。我们可以用两种方式进行轮询:短轮询和长轮询。简单来说,短轮询是基于AJAX的定时器,按固定延迟调用,而长轮询则基于Comet(即服务器在发生服务器事件时无延迟地向客户端发送数据)。两者都有优点和缺点,适合不同的用例。有关详细信息,请阅读StackOverflow社区提供的答案。

让我们看看一个简单的客户端长轮询代码片段:

/* Client - subscribing to the github events */
subscribe: (callback) => {
    const pollUserEvents = () => {
        $.ajax({
            method: 'GET',
            url: 'http://localhost:8080/githubEvents', 
            success: (data) => {
                callback(data) // process the data
            },
            complete: () => {
                pollUserEvents();
            },
            timeout: 30000
        })
    }
    pollUserEvents()
}

这是一个基本的长轮询函数,第一次运行与通常情况下一样,但它设置了30秒的超时,在每个Async Ajax调用之后,回调再次调用Ajax。

AJAX调用在HTTP协议上工作,这意味着对同一域的请求应默认被多路复用。我们发现了一些这种方法的缺陷。

  • 多路复用(轮询响应无法真正同步)
  • 轮询需要3次往返(TCP SIN、SSL和数据)
  • 超时(如果连接闲置时间太长,代理服务器将关闭连接)

你可以在此处阅读更多实际挑战的内容。

2. 使用WebSockets:

WebSocket只是客户端和服务器之间的持久连接。这是一种通信协议,提供了单个TCP连接上的全双工通信通道。

RFC 6455指出,WebSocket“旨在通过HTTP端口80和443工作,并支持HTTP代理和中介”,因此与HTTP协议兼容。为了实现兼容性,WebSocket握手使用HTTP Upgrade头将从HTTP协议更改为WebSocket协议。HTTP和WebSocket都位于OSI模型的应用层,并因此依赖于第4层的TCP。

有一个MDN文档详细解释了WebSocket,我鼓励你也阅读一下。

让我们看看一个非常简单的WebSocket客户端实现可能是什么样子:

$(function () {
  // if user is running mozilla then use it's built-in WebSocket
  window.WebSocket = window.WebSocket || window.MozWebSocket;

  const connection = new WebSocket('ws://localhost:8080/githubEvents');

  connection.onopen = function () {
    // connection is opened and ready to use
  };

  connection.onerror = function (error) {
    // an error occurred when sending/receiving data
  };

  connection.onmessage = function (message) {
    // try to decode json (I assume that each message
    // from server is json)
    try {
      const githubEvent = JSON.parse(message.data); // display to the user appropriately
    } catch (e) {
      console.log('This doesn\'t look like a valid JSON: '+ message.data);
      return;
    }
    // handle incoming message
  };
});

如果服务器支持WebSocket协议,它将同意升级,并通过响应中的Upgrade头通信。

让我们看看如何在Node.JS(服务器)中实现它:

const express = require('express');
const events = require('./events');
const path = require('path');

const app = express();

const port = process.env.PORT || 5001;

const expressWs = require('express-ws')(app);

app.get('/', function(req, res) {
	res.sendFile(path.join(__dirname + '/static/index.html'));
});

app.ws('/', function(ws, req) {
	const githubEvent = {}; // sample github Event from Github event API https://api.github.com/events
	ws.send('message', githubEvent);
});

app.listen(port, function() {
	console.log('Listening on', port);
});

一旦我们从GitHub事件API获取数据,我们就可以在建立连接后将其流式传输到客户端。对于我们的情况,这种方法也有一些缺陷。

  • 对于WebSockets,我们需要处理HTTP本身已经处理的许多问题。
  • WebSocket是一种不同的传输数据的协议,它不会自动多路复用HTTP/2连接。在服务器和客户端上实现自定义多路复用有点复杂。
  • WebSocket是基于帧而不是基于流的。当我们打开网络选项卡时,可以看到WebSocket消息列在帧下面。

有关WebSocket的详细信息,请查看这篇精彩文章,在这里你可以阅读有关分段以及如何在幕后处理它的更多信息。

3. 使用SSE:

SSE是一种机制,允许服务器在客户端 - 服务器连接建立后异步向客户端推送数据。然后,服务器可以在每个新的“块”可用时决定发送数据。它可以被认为是一种单向发布-订阅模型。

它还提供了一个名为EventSource的标准JavaScript客户端API,由W3C作为HTML5标准的一部分在大多数现代浏览器中实现。为不支持EventSource API的浏览器提供了Polyfills。

这是一个使用SSE的简单示例:

https://caniuse.com/#feat=eventsource

我们可以看到,Edge和Opera Mini在这个实现方面落后了,而SSE最重要的应用场景是移动浏览器设备,在这些浏览器中它们的市场份额微乎其微。Yaffle是Event Source的一个著名的polyfill。

由于SSE基于HTTP,它与HTTP/2自然契合,并且可以结合起来以获得最佳效果:HTTP/2处理基于多路复用的流的高效传输层,而SSE提供API以使应用程序能够实现推送。因此,我们可以轻松获得在HTTP/2上进行多路复用的功能。当连接断开时,客户端和服务器都会被通知。通过在消息中维护唯一的ID,服务器可以看到客户端错过了多少条消息,并在重新连接时发送错过的消息的记录。

让我们看看一个简单的客户端实现示例:

const evtSource = new EventSource('/events');

 evtSource.addEventListener('event', function(evt) {
      const data = JSON.parse(evt.data);
      // Use data here
 },false);

这个片段非常简单。它连接到我们的源并等待接收消息。现在,示例NodeJS服务器将看起来像这样。

我们从这种方法中获得的主要优点是:

// events.js
const EventEmitter = require('eventemitter3');
const emitter = new EventEmitter();

function subscribe(req, res) {

	res.writeHead(200, {
		'Content-Type': 'text/event-stream',
		'Cache-Control': 'no-cache',
		Connection: 'keep-alive'
	});

	// Heartbeat
	const nln = function() {
		res.write('\n');
	};
	const hbt = setInterval(nln, 15000);

	const onEvent = function(data) {
		res.write('retry: 500\n');
		res.write(`event: event\n`);
		res.write(`data: ${JSON.stringify(data)}\n\n`);
	};

	emitter.on('event', onEvent);

	// Clear heartbeat and listener
	req.on('close', function() {
		clearInterval(hbt);
		emitter.removeListener('event', onEvent);
	});
}

function publish(eventData) {
  // Emit events here recieved from Github/Twitter APIs
	emitter.emit('event', eventData);
}

module.exports = {
	subscribe, // Sending event data to the clients 
	publish // Emiting events from streaming servers
};

// App.js
const express = require('express');
const events = require('./events');
const port = process.env.PORT || 5001;
const app = express();

app.get('/events', cors(), events.subscribe);

app.listen(port, function() {
   console.log('Listening on', port);
});
  • 实现更简单、数据更有效率
  • 它可以自动地在HTTP/2上进行多路复用
  • 限制了客户端对数据的连接数为1

如何在SSE、WebSocket和轮询之间选择?

在长时间的、详尽的客户端和服务器实现之后,看起来SSE是我们解决数据传递问题的最终答案。它也存在一些问题,但可以解决。

一些可以利用Server-Sent Events的简单应用示例:

  • 流媒体股票价格实时图表
  • 重要事件的实时新闻报道(发布链接、推文和图片)
  • 由Twitter的流API提供数据的实时Github / Twitter仪表板墙
  • 用于显示服务器统计信息(如正常运行时间、健康状况和正在运行的进程)的监视器。

然而,SSE不仅是传递快速更新的其他方法的可行替代方案。每种方法在一些特定的场景下都占优势,就像在我们的情况下,SSE被证明是一种理想的解决方案。考虑一种需要大量来自连接两端的消息的场景,比如大型多人在线游戏(MMO)。在这种情况下,WebSockets占优势。

如果你的用例需要显示实时市场新闻、市场数据、聊天应用程序等,像我们的情况一样依赖于HTTP/2 + SSE将为你提供一个高效的双向通信通道,同时从保持在HTTP世界中获得的好处。

如果你想获取我们用例的一个示例客户端-服务器实现,请查看GitHub代码

译自:https://codeburst.io/polling-vs-sse-vs-websocket-how-to-choose-the-right-one-1859e4e13bd9

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
一个人玩
先找到想要的,然后出发

评论(0)

添加评论