在HTML5规范下,最推荐使用ServerSent和WebSocket的方式进行服务器消息的推送,对比这两种方式:
- ServerSent Events的方式(简称 SSE),可以使服务端的开发依然依用以前的方式,但是其工作方式与Comet类似。
- 而WebSocket的方式,则对服务端的开发有着较高的要求,但其工作方式是完全的推送。
Websocket
- WebSocket是HTML5中的协议,支持持久连接
- WebSocket 是一个双向通信协议,它在握手阶段采用 HTTP/1.1 协议(暂时不支持 HTTP/2)
- WebSocket 提供两种协议:不加密的 ws:// 和 加密的 wss://
因为是用 HTTP 握手,它和 HTTP 的端口:ws 是 80(HTTP),wss 是 443(HTTPS)
Websocket API
var ws = new WebSocket(“ws:echo.websocket.org”);
ws.onopen = function(){ws.send(“Test!”); };
ws.onmessage = function(evt){console.log(evt.data);
ws.close();};
ws.onclose = function(evt){console.log(“WebSocketClosed!”);};
ws.onerror = function(evt){console.log(“WebSocketError!”);};
握手过程
1 首先客户端向服务端发起一个特殊的 HTTP 请求,其消息头如下:
GET /chat HTTP/1.1 # 请求行
Origin: http://example.com #用于防止未认证的跨域脚本使用浏览器 websocket api 与服务端进行通信
Connection: Upgrade //请求
Host: server.example.com
Upgrade: websocket #服务器支持WebSocket协议它将同意
#16bits 编码得到的 base64 串
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
#可选:子协议协商字段
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
2 如果服务端支持该版本的 WebSocket,会返回 101 响应,响应标头如下:
HTTP/1.1 101 Switching Protocols // 状态行
Upgrade: websocket // required
Connection: Upgrade // required
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= // required,加密后的 Sec-WebSocket-Key
Sec-WebSocket-Protocol: chat // 表明选择的子协议
握手完成后,接下来的 TCP 数据包就都是 WebSocket 协议的帧了。握手不是 TCP 的握手,而是在 TCP 连接内部,从 HTTP/1.1 upgrade 到 WebSocket 的握手。
工具与实现
- aiohttp :提供了 WebSocket 支持。
- python :可使 websockets 实现的异步 WebSocket 客户端与服务端。(代码略)
python 查看协议版本号
>>> import requests
>>> resp = requests.get("https://zhihu.com")
>>> resp.raw.version
- node.js中websocket的实现
var WebSocketServer = require('websocket').server;
var http = require('http');
var server = http.createServer(function(request, response) {
// process HTTP request.
});
server.listen(1337, function() { });
// create the server
wsServer = new WebSocketServer({
httpServer: server
});
// WebSocket server
wsServer.on('request', function(request) {
var connection = request.accept(null, request.origin);
// This is the most important callback for us, we'll handle
// all messages from users here.
connection.on('message', function(message) {
// Process WebSocket message
});
connection.on('close', function(connection) {
// Connection closes
});
});
- java语言相关代码(待续)
SSE 与 HTTP2
- http2的介绍-自行百度
服务端推送事件,是通过 HTTP 长连接进行信息推送的一个功能。
它首先由浏览器向服务端建立一个 HTTP 长连接,然后服务端不断地通过这个长连接将消息推送给浏览器。
严格地说,HTTP 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(streaming)。也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。SSE 就是利用这种机制,使用流信息向浏览器推送信息。它基于 HTTP 协议,目前除了 IE/Edge,其他浏览器都支持。
客户端API
SSE 的客户端 API 部署在EventSource对象上。下面的代码可以检测浏览器是否支持 SSE。
if ('EventSource' in window) {
...
}
- 浏览器首先生成一个EventSource实例,向服务器发起连接。
// create SSE connection
var url='/dates'
var source = new EventSource(url);
#可以跨域,跨域的方式则代码发如下
# 打开withCredentials属性,表示是否一起发送 Cookie。
var source = new EventSource(url, { withCredentials: true });
EventSource实例的readyState属性
- 0:相当于常量EventSource.CONNECTING,表示连接还未建立,或者断线正在重连。
- 1:相当于常量EventSource.OPEN,表示连接已经建立,可以接受数据。
- 2:相当于常量EventSource.CLOSED,表示连接已断,且不会重连。
- 连接建立时,这些 API 和 WebSocket 的很相似。
连接一旦建立,就会触发open事件,可以在onopen属性定义回调函数。
source.onopen = function(event) {
// handle open event
};
# 另外一种写法
// 另一种写法
source.addEventListener('open', function (event) {
// ...
}, false);
- 客户端收到服务器发来的数据,就会触发message事件,可以在onmessage属性的回调函数。
source.onmessage = function(event) {
# 发送过来的实际数据(string)
var data = event.data;
# 服务器端URL的域名部分,即协议、域名和端口。
var origin = event.origin;
# 数据的编号,由服务器端发送。如果没有编号,这个属性为空。
var lastEventId = event.lastEventId;
# handle message
# TODO: 处理消息
};
# onmessage的另一种写法(onerror相同)
source.addEventListener('message', function (event) {
var data = event.data;
handle message
}, false);
- 如果发生通信错误(比如连接中断),就会触发error事件,可以在onerror属性定义回调函数。
source.onerror = function(event) {
// handle error event
};
// 另一种写法
source.addEventListener('error', function (event) {
// handle error event
}, false);
- 关闭连接
source.close();
- 自定义事件
默认情况下,服务器发来的数据,总是触发浏览器EventSource实例的message事件。开发者还可以自定义 SSE 事件,这种情况下,发送回来的数据不会触发message事件。
source.addEventListener('foo', function (event) {
var data = event.data;
// handle message
}, false);
上面代码中,浏览器对 SSE 的foo事件进行监听。如何实现服务器发送foo事件,请看下文。
服务器实现
- 数据格式
- 头部数据
在收到客户端的 SSE 请求(HTTP 协议)时,服务端返回的响应首部如下:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
上面三行之中,第一行的Content-Type必须指定 MIME 类型为event-steam。
- BODY 部分
每一次发送的信息,由若干个message组成,每个message之间用\n\n分隔。每个message内部由若干行组成,每一行都是如下格式。
[field]: value\n
上面的field,SSE 定义了四种信息:
- data:数据栏
- event:自定义数据类型
- id :数据 id
- retry:最大间隔时间,超时则重新连接
此外还可以有冒号开头的行,表示注释。通常,服务器每隔一段时间就会向浏览器发送一个注释,保持连接不中断。
完整的例子
: this is a test stream\n\n
data: some text\n\n
data: another message\n
data: with two lines \n\n
下面是一个发送 JSON 数据的例子。
data: {\n
data: "foo": "bar",\n
data: "baz", 555\n
data: }\n\n
Node 服务器实例(其他待续)
var http = require("http");
http.createServer(function (req, res) {
var fileName = "." + req.url;
if (fileName === "./stream") {
res.writeHead(200, {
"Content-Type":"text/event-stream",
"Cache-Control":"no-cache",
"Connection":"keep-alive",
"Access-Control-Allow-Origin": '*',
});
res.write("retry: 10000\n");
res.write("event: connecttime\n");
res.write("data: " + (new Date()) + "\n\n");
res.write("data: " + (new Date()) + "\n\n");
interval = setInterval(function () {
res.write("data: " + (new Date()) + "\n\n");
}, 1000);
req.connection.addListener("close", function () {
clearInterval(interval);
}, false);
}
}).listen(8844, "127.0.0.1");
JAVA 这 GRPC over HTTP2 (代码待续)
- gRPC 完全隐藏了 HTTP/2 本身的 method、headers、path 等语义,这些信息对用户而言完全不可见了。
求统一使用 POST,响应状态统一为 200。只要响应是标准的 gRPC 格式,响应中的 HTTP 状态码将被完全忽略。
- gRPC 定义了自己的 status 状态码、格式固定的 path、还有它自己的 headers。
SSE与HTTP2 、Wocket比较
虽然HTTP/2提供了很多功能,但它并没有完全满足对现有推送/流技术的需求。
SSE 与 WebSocket 作用相似,都是建立浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息。比较HTTP/2和WebSocket,可以看到很多相似之处:
![](https://img.haomeiwen.com/i10650660/fdf95867aba02e80.png)
- SSE 使用 HTTP 协议,现有的服务器软件都支持。WebSocket 是一个独立协议。
- SSE 属于轻量级,使用简单;WebSocket 协议相对复杂。
- SSE 默认支持断线重连,WebSocket 需要自己实现。
- SSE 一般只用来传送文本,二进制数据需要编码后传送,WebSocket 默认支持传送二进制数据。
- SSE 支持自定义发送的消息类型。
如何选择WebSocket和HTTP/2?
WebSockets 会在 HTTP/2 + SSE 的领域中生存下来,主要是因为它是一种已经被很好地应用的技术,并且在非常具体的使用情况下,它比 HTTP/2 更具优势,因为它已经被构建用于具有较少开销(如报头)的双向功能。
假设建立一个大型多人在线游戏,需要来自连接两端的大量消息。在这种情况下,WebSockets 的性能会好很多。
一般情况下,只要需要客户端和服务器之间的真正低延迟,接近实时的连接,就使用 WebSocket ,这可能需要重新考虑如何构建服务器端应用程序,以及将焦点转移到队列事件等技术上。
使用的方案需要显示实时的市场消息,市场数据,聊天应用程序等,依靠 HTTP/2 + SSE 将为你提供高效的双向通信渠道,同时获得留在 HTTP 领域的各种好处:
- 考虑到与现有 Web 基础设施的兼容性时,WebSocket 通常会变成一个痛苦的源头,因为它将 HTTP 连接升级到完全不同于 HTTP 的协议。
- 规模和安全性:Web 组件(防火墙,入侵检测,负载均衡)是以 HTTP 为基础构建,维护和配置的,这是大型/关键应用程序在弹性,安全性和可伸缩性方面更偏向的环境。
总体来说,WebSocket 更强大和灵活。因为它是全双工通道,可以双向通信;SSE 是单向通道,只能服务器向浏览器发送,因为流信息本质上就是下载。如果浏览器向服务器发送信息,就变成了另一次 HTTP 请求。
网友评论