SSE则是部署在 HTTP协议之上的,现有的服务器软件都支持此协议。
SSE是一个轻量级协议,相对简单;WebSocket是一种较重的协议,相对复杂。但SSE只支持单向交互(服务器给客户发送),Websocket支持双向交互。
SSE默认支持断线重连,WebSocket则需要额外部署。
数据格式方面, SSE 使用的是 UTF8 编码的文本格式。
SSE的HTTP response 里header Content-Type 的值是 text/event-stream。不可变!
SSE的数据格式
每个SSE的消息响应分为4个元素:
- retry:重试时间,单位毫秒,只能为数字(SSE请求失败,就会发送新的请求)
- id:消息ID(自定义)
- event:时间类型(自定义)
- data:消息的内容(自定义)
下图是4个消息,注意,多个消息之间中间会有个空行(\n\n)。单个消息之间元素间隔是换行\n
retry: 5000 id: 1cd7bb64-4341-4f5d-a690-4298b8a8ae20 event: eventType data: Sun Nov 20 18:23:11 CST 2022 retry: 5000 id: 2f57295d-3eaa-4e5c-a787-55fff58d9b05 event: eventType data: Sun Nov 20 18:23:12 CST 2022 retry: 5000 id: 6a9618de-99e7-4c03-91f8-dcdd7601a8d0 event: eventType data: Sun Nov 20 18:23:13 CST 2022 retry: 5000 id: c5fdcc90-b1f7-4058-9a3a-d63881ffea8b event: eventType data: Sun Nov 20 18:23:14 CST 2022
使用示例:
golang
安装包:
go get gopkg.in/antage/eventsource.v1
广播模式SSE
广播模式SSE指不设置事件名,或者说是不设置通道,所有客户端接收同样的数据。
main.go
package main import ( "fmt" "gopkg.in/antage/eventsource.v1" "log" "net/http" "time" ) // 广播模式SSE,不设置事件名称(也可理解为通道) func main() { es := eventsource.New(nil, nil) defer es.Close() http.Handle("/", http.FileServer(http.Dir("./public"))) http.Handle("/events", es) go func() { for { // 只设置发送数据,不添加事件名 es.SendEventMessage(fmt.Sprintf("send data: %s", time.Now().Format("2006-01-02 15:04:05")), "", "") log.Printf("客户端连接数: %d", es.ConsumersCount()) time.Sleep(2 * time.Second) } }() log.Println("Open URL http://localhost:8080/ in your browser.") err := http.ListenAndServe(":8080", nil) if err != nil { log.Fatal(err) } }
public/index.html
<!DOCTYPE html> <html> <head> <title>SSE test</title> <script type="text/javascript"> const es = new EventSource("http://localhost:8080/events"); es.onmessage = function (e) { document.getElementById("test") .insertAdjacentHTML("beforeend", "<li>" + e.data + "</li>"); } es.onerror = function (e) { // readyState说明 // 0:浏览器与服务端尚未建立连接或连接已被关闭 // 1:浏览器与服务端已成功连接,浏览器正在处理接收到的事件及数据 // 2:浏览器与服务端建立连接失败,客户端不再继续建立与服务端之间的连接 console.log("readyState = " + e.currentTarget.readyState); } </script> </head> <body> <h1>SSE test</h1> <div> <ul id="test"> </ul> </div> </body> </html>
启动服务:
go run main
点对点模式SSE
实现方式和广播模式差不多,只需做简单修改:
服务端代码只需添加事件名称:
// 设置事件名称为:test-event es.SendEventMessage(fmt.Sprintf("send data: %s", time.Now().Format("2006-01-02 15:04:05")), "test-event", "")
前端修改接收方式
es.addEventListener("test-event", (e) => { document.getElementById("test") .insertAdjacentHTML("beforeend", "<li>" + e.data + "</li>"); });
支持跨域的SSE
现在项目开发基本上都是前后端分离,这样就会存在跨域问题,SSE解决跨域的方式只需要在new
方法内增加允许跨域请求头:
es := eventsource.New( eventsource.DefaultSettings(), func(req *http.Request) [][]byte { return [][]byte{ []byte("X-Accel-Buffering: no"), []byte("Access-Control-Allow-Origin: *"), } })
前端创建sse连接时也可添加允许跨域参数:
const es = new EventSource("http://localhost:8080/events", { withCredentials: true });
解决火狐浏览器断开不会自动重连问题
在Chrome浏览器中sse断开后会自动重连,但firefox浏览器中断开后不会重连,解决办法是,前端js通过判断连接状态主动进行重连请求,通过判断readyState的值进行重新调用初始化操作
readyState说明:
- 0:浏览器与服务端尚未建立连接或连接已被关闭
- 1:浏览器与服务端已成功连接,浏览器正在处理接收到的事件及数据
- 2:浏览器与服务端建立连接失败,客户端不再继续建立与服务端之间的连接
前端代码可修改为如下:
<!DOCTYPE html> <html> <head> <title>SSE test</title> <script type="text/javascript"> let es = null; // 解决火狐浏览器断开不会自动重连问题 function initES() { if (es == null || es.readyState == 2) { es = new EventSource("http://localhost:8080/events", {withCredentials: true}); es.addEventListener("test-event", (e) => { document.getElementById("test") .insertAdjacentHTML("beforeend", "<li>" + e.data + "</li>"); }); es.onerror = function (e) { // readyState说明 // 0:浏览器与服务端尚未建立连接或连接已被关闭 // 1:浏览器与服务端已成功连接,浏览器正在处理接收到的事件及数据 // 2:浏览器与服务端建立连接失败,客户端不再继续建立与服务端之间的连接 console.log("readyState = " + e.currentTarget.readyState); if (es.readyState == 2) { setTimeout(initES, 5000) } } } } initES() </script> </head> <body> <h1>SSE test</h1> <div> <ul id="test"> </ul> </div> </body> </html>