HTML5 服务器发送事件(Server-Sent Events) 使用。
我们都知道服务端与客户端之间通信现在多数用 WebSocket
,在此之前,还有传统 Ajax
短轮询、Comet
技术,以及本文要提及的 SSE(Server-sent Events)
。
What
服务器推送事件(Server-sent Events),简称SSE,是 HTML 5 规范中的一个组成部分,是一种服务器向客户端发送事件和数据的单向通讯协议。客户端不会关闭连接,会一直等着服务器发过来的新的数据流,当客户端收到消息后会再次发送请求,周而复始。
When
基于上述定义,可以知道网页客户端自动获取来自服务器的更新数据,所以 SSE 适合诸如主从控制场景、服务端下发消息无互动、自动更新、物联网信息下发等。
Support
浏览器支持情况:主流浏览器均支持服务器发送事件,除了 IE👎。
How
带着以上认知,我们可以简单搭建一个 demo,用来演示服务端发送消息给网页客户端。其中服务端实现协议部分,而网页端使用的主要 API 为 EventSource
:通过 new EventSource(url)
生成实例对象建立连接,然后就是处理事件的监听:
- open:
onopen/addEventListener("open", function(event) {// handle open event}, false)
- message:
onMessage/addEventListener("message",function(event) {// handle open event}, false))
- error:
onError/addEventListener("error",function(event) {// handle open event}, false))
- 自定义事件:
addEventListener("cfz",function(event) {// handle open event}, false))
其中,event
对象中属性有data(服务端数据)
、origin(服务器端URL的域名部分)
、lastEventId(服务端发送的数据的编号)
Enviroment
- go 1.13.7
- Win 10 x64
- goland 2019
选择 golang 是因为我们可以更方便地模拟跨线程处理消息的情况——服务端(S)接收一方(M)发来的消息,然后下发到客户端(C)。这其中 M 端使用 POST 请求发送数据,S 端利用协程将处理好的数据下发给 C。C 收到信息后,会再次请求服务端数据。
S 端
我们采用 gin
作为服务处理以及转发请求,另外就是加载 HTML 模拟 M 和 C 的动作:
创建服务器
1
2
3
4
5
6
7app := gin.Default()
sseRouter := app.Group(`/sse`, func(context *gin.Context) {})
// 逻辑处理
//...
if err := app.Run(fmt.Sprintf(":%d", 9529)); err != nil {
log.Fatal(err)
}处理请求:
gin
中已经封装好相关的逻辑,调用即可,PS:其实不用gin
,用 golang 本身的http
库也完全可以实现,发送的时候,类型记得要设置成text/event-stream
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35import "github.com/gin-gonic/gin"
// 消息管道
var msgChan = make(chan string, 10)
// M 发送消息
sseRouter.POST("/move.cfz", func(context *gin.Context) {
msg := context.PostForm("msg")
log.Printf("receive from M: %s", msg)
go func(m string) {
// 发送
msgChan <- m
}(msg)
})
// C 端接收消息
sseRouter.GET("/go.cfz", func(context *gin.Context) {
w := context.Writer
h := w.Header()
//h.Set("Cache-Control", "no-cache")
//h.Set("Connection", "keep-alive")
h.Set("Content-Type", "text/event-stream")
//h.Set("X-Accel-Buffering", "no")
if msg, ok := <-msgChan; ok {
event := sse.Event{
Id: time.Now().String(),
Event: "message",
Data: msg,
}
if e := sse.Encode(w, event); e != nil {
log.Println(e.Error())
return
}
log.Printf("send to C: %s", msg)
}
})
浏览器端处理
C 端实现 SSE 方法即可,简单易用:
1 | if (!window.EventSource) { |
1 | <div id="message"></div> |
M 端发送消息:
1 | function send() { |
1 | <div id="message"> |
The End
通过以上 demo,我们可以知道在数据单向流动时,选择 SSE
是比较好的,除了协议足够简单外,在各个语言的实现也更容易,基于现有的 HTTP(s)
服务即可,并且支持断线重连和自定义数据格式;而 WebSocket
则需要升级协议,适用于双向通讯的情况。总而言之,两者都是 HTML 5
规范里的标准技术,对于解决遗留的轮询等技术问题能达到很好的效果。
Reference
- [1] SSE:服务器发送事件
- [2] SSE技术详解:一种全新的HTML5服务器推送事件技术
- [3] HTML5 SSE
- [4] Java SSE 实现