[TOC]
- SSESSE(Server-sent Events)介绍1.SSE能做什么?2.SSE优势目录结构代码示例
SSE
SSE(Server-sent Events)介绍
SSE(Server-sent Events是WebSocket的一种轻量代替方案,使用HTTP协议
严格地说,HTTP协议是没有办法做服务器推送的,但是当服务器向客户端声明接下来要发送流信息时,客户端就会保持连接打开,SSE使用的就是这种原理
1.SSE能做什么?
理论上,SSE和WebSocket做的是同一件事情。当你需要用新数据局部更新网络应用时,SSE可以做到不需要用户执行任何操作,便可以完成
举例我们要做一个统计系统的管理后台,我们想知道统计数据的实时情况。类似这种更新频繁、 低延迟的场景,SSE可以完全满足。
其他一些应用场景:例如邮箱服务的新邮件提醒,微博的新消息推送、管理后台的一些操作实时同步等,SSE都是不错的选择
SSE是单向通道,只能服务器向客户端发送消息,如果客户端需要向服务器发送消息,则需要一个新的HTTP请求。这对比WebSocket的双工通道来说,会有更大的开销。这么一来的话就会存在一个「什么时候才需要关心这个差异?」的问题,如果平均每秒会向服务器发送一次消息的话,那应该选择WebSocket。如果一分钟仅 5 - 6 次的话,其实这个差异并不大
2.SSE优势
- 实现一个完整的服务仅需要少量的代码
- 可以在现有的服务中使用,不需要启动一个新的服务
- 可以用任何一种服务端语言中使用
- 基于HTTP/HTTPS协议,可以直接运行于现有的代理服务器和认证技术
目录结构
主目录sse
—— main.go —— optional.sse.mini.js.html
代码示例
main.go
// Package main显示如何通过代理通过SSE向客户端发送连续事件消息。//阅读详情:https://www.w3schools.com/htmL/html5_serversentevents.asp// https://robots.thoughtbot.com/writing-a-server-sent-events-server-in-gopackage mainimport ( "encoding/json" "fmt" "time" "github.com/kataras/golog" "github.com/kataras/iris" //注意: 由于某种原因,最新的vscode-go语言扩展不能提供足够智能帮助(参数文档并转到定义功能) //对于`iris.Context`别名,因此如果您使用VS Code,则导入`Context`的原始导入路径,它将执行此操作: "github.com/kataras/iris/context")//Broker拥有开放的客户端连接//在其Notifier频道上侦听传入事件//并将事件数据广播到所有已注册的连接type Broker struct { //主要事件收集例程将事件推送到此频道 Notifier chan []byte //新的客户端连接 newClients chan chan []byte //关闭客户端连接 closingClients chan chan []byte //客户端连接注册表 clients map[chan []byte]bool}// NewBroker返回一个新的代理工厂func NewBroker() *Broker { b := &Broker{ Notifier: make(chan []byte, 1), newClients: make(chan chan []byte), closingClients: make(chan chan []byte), clients: make(map[chan []byte]bool), } //设置它正在运行 - 收听和广播事件 go b.listen() return b}//听取不同的频道并采取相应应对func (b *Broker) listen() { for { select { case s := <-b.newClients: //新客户端已连接 //注册他们的消息频道 b.clients[s] = true golog.Infof("Client added. %d registered clients", len(b.clients)) case s := <-b.closingClients: //客户端已离线,我们希望停止向其发送消息。 delete(b.clients, s) golog.Warnf("Removed client. %d registered clients", len(b.clients)) case event := <-b.Notifier: //我们从外面得到了一个新事件 //向所有连接的客户端发送事件 for clientMessageChan := range b.clients { clientMessageChan <- event } } }}func (b *Broker) ServeHTTP(ctx context.Context) { //确保编写器支持刷新 flusher, ok := ctx.ResponseWriter().Flusher() if !ok { ctx.StatusCode(iris.StatusHTTPVersionNotSupported) ctx.WriteString("Streaming unsupported!") return } //设置与事件流相关的header,如果发送纯文本,则可以省略“application/json” //如果你开发了一个go客户端,你必须设置:“Accept”:“application/json,text/event-stream”header ctx.ContentType("application/json, text/event-stream") ctx.Header("Cache-Control", "no-cache") ctx.Header("Connection", "keep-alive") //我们还添加了跨源资源共享标头,以便不同域上的浏览器仍然可以连接 ctx.Header("Access-Control-Allow-Origin", "*") //每个连接都使用Broker的连接注册表注册自己的消息通道 messageChan := make(chan []byte) //通知我们有新连接的Broker b.newClients <- messageChan //监听连接关闭以及整个请求处理程序链退出时(此处理程序)并取消注册messageChan。 ctx.OnClose(func() { //从已连接客户端的map中删除此客户端,当这个处理程序退出时 b.closingClients <- messageChan }) //阻止等待在此连接的消息上广播的消息 for { //写入ResponseWriter // Server Sent Events兼容 ctx.Writef("data: %s\n\n", <-messageChan) //或json:data:{obj} //立即刷新数据而不是稍后缓冲它 flusher.Flush() }}type event struct { Timestamp int64 `json:"timestamp"` Message string `json:"message"`}const script = `<script type="text/javascript">if(typeof(EventSource) !== "undefined") { console.log("server-sent events supported"); var client = new EventSource("http://localhost:8080/events"); var index = 1; client.onmessage = function (evt) { console.log(evt); // it's not required that you send and receive JSON, you can just output the "evt.data" as well. dataJSON = JSON.parse(evt.data) var table = document.getElementById("messagesTable"); var row = table.insertRow(index); var cellTimestamp = row.insertCell(0); var cellMessage = row.insertCell(1); cellTimestamp.innerHTML = dataJSON.timestamp; cellMessage.innerHTML = dataJSON.message; index++; window.scrollTo(0,document.body.scrollHeight); };} else { document.getElementById("header").innerHTML = "<h2>SSE not supported by this client-protocol</h2>";}</script>`func main() { broker := NewBroker() go func() { for { time.Sleep(2 * time.Second) now := time.Now() evt := event{ Timestamp: now.Unix(), Message: fmt.Sprintf("Hello at %s", now.Format(time.RFC1123)), } evtBytes, err := json.Marshal(evt) if err != nil { golog.Error(err) continue } broker.Notifier <- evtBytes } }() app := iris.New() app.Get("/", func(ctx context.Context) { ctx.HTML( `<html><head><title>SSE</title>` + script + `</head> <body> <h1 id="header">Waiting for messages...</h1> <table id="messagesTable" border="1"> <tr> <th>Timestamp (server)</th> <th>Message</th> </tr> </table> </body> </html>`) }) app.Get("/events", broker.ServeHTTP) // http://localhost:8080 // http://localhost:8080/events app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed))}
optional.sse.mini.js.html
<!-- 你可以把它放到你最喜欢的浏览器 --><html><head> <title>SSE(javascript side)</title> <script type="text/javascript"> var client = new EventSource("http://localhost:8080/events") client.onmessage = function (evt) { console.log(evt) } </script></head><body> <h1>打开浏览器控制台(F12)并观察传入的事件消息</h1></body></html>
