我们可以使用以下三种方式来创建一个实时的web应用程序
- Long polling or short polling(client pull)
- WebSocket(server push)
- Server-Sent Events(server push)
client pull: 客户端以一定的间隔来请求server来获取更新数据
server push:
服务器主动向客户端推送更新
long polling
这里简单说一下 short polling 和 long polling 的区别, poll是客户端代码中每隔一段时间访问一次server(for 循环访问)long polling 是用 Transfer-Encoding: chunked (也是 http comet)的方式 设置一个超长的超时时间,每当服务端有更新则用这个链接 一个chunk 一个chunk 返回给客户端数据。
http comet 除了使用long polling 还用 iframe
Using WebSockets
websocket 本质上就是一个贯穿 client & server的一个链接,websocket协议在一条tcp链接上提供了全双工的通信信道。
RFC 6455 上注明,websocket 是一个被设计在http 80 & 433 端口上的并且兼容http代理和中介的协议, 这使得 websocket 基本上完全兼容http协议, 为了实现完全兼容性,websocket 握手协议使用http 的 Upgrade 请求头 去将http协议换成websocket协议,
http 协议 和 websocket 协议都是建立在tcp协议之上的 七层协议
client:
$(function () {
window.Websocket = window.Websocket || window.MozWebSocket;
const connection = new WebSocket('ws://localhost:8080/githubEvents');
connection.onopen = function(){}
connection.onerror = function(error){}
connection.onmessage = function(message){}
})
server:
const express = require('express')
const event = 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){});
app.ws('/', function(ws, req){
const githubEvent = {}
ws.send('message', githubEvent)
})
app.listen(port, function(){})
some pitfalls
- 如果使用websocket, 我们得处理很多http帮我们自动处理的东西
- websocket是一种用于传输数据的协议,它并不会兼容http/2的链接, 在客户端&服务端自己实现多路复用有些复杂
- websocket 是基于frame的, 并不是基于流的。
Using SSE
SSE 是一种可以让服务端在已建立的链接上 push data 到客户端的协议,可以被考虑成 单向发布-订阅的模型,它并不像websocket一样是双向数据协议, 只是从服务器向客户端推数据这一单项的数据流, 是在HTML5 中新加入的。
通过实现 javascript EventSource
来编写客户端。
由于SSE是基于http的, 所以SSE可以天然支持HTTP/2, SSE可以和HTTP/2 结合使用。 HTTP/2 处理基于多路复用流的高效传输层, SSE 则为应用进程提供api来启动推送功能
client:
const evtSource = new EventSource('/evnets')
evtSource.addEventListener('event', function(evt) {const data = JSON.parse(evt.data)}, false);
我们使用Python的generator 来进行数据推送, 因为它可以不断生产出数据来进行推送, 与SSE的场景十分吻合。
server in flask
@route("/events")
def stream():
def eventStream():
while True:
# Poll data from the database
# and see if there's a new message
if len(messages) > len(previous_messages):
yield "data:
{}\n\n".format(messages[len(messages)-1)])"
return Response(eventStream(), mimetype="text/event-stream")
SSE 主要有以下三点好处
- 更简单的实现方法和更高效的数据传输效率
- 可以集成HTTP/2 实现多路复用(一个浏览器与server只使用一个http链接)
- 可以限制客户端上的连接数
我们应该如何选择 SSE, WebSocket and polling
按照上面的例子来看, 我们似乎可以认为SSE是最适合的,但是SSE也存在一些问题等待我们解决。
以下是我们可以使用SSE的几个例子
- 流媒体股票价格实时图表
- 实时重要新闻的报道(发布链接,推送文和图片)
- 由twitter或者是github的流媒体推送的实时仪表盘
- 各种系统监控
也有一些SSE不适合的场景, 比如像MMO(大型多人在线)游戏这样的场景, 它需要来自链接两端大量的消息(全双工)这时 websocket 则占据完全优势。
如果你的使用场景是 实时显示市场新闻,股市数据,聊天记录之类的, 那么 HTTP/2 + SSE 将会给你提供非常高效的数据传输方式
参考: https://codeburst.io/polling-vs-sse-vs-websocket-how-to-choose-the-right-one-1859e4e13bd9