关于服务端和客户端即时消息我所知道的方式有三种:轮询、websocket和SSE,去百度很多文章都是或多或少借用一些框架实现并且有些模凌两可,这篇文章主要记录一下用go不借助第三方库实现一个简单的SSE服务端推送。
SSE通常来说,一个网页获取新的数据通常需要发送一个请求到服务器,也就是向服务器请求的页面。使用服务器发送事件,服务器可以随时向我们的 Web 页面推送数据和信息。这些被推送进来的信息可以在这个页面上以 事件 + 数据 的形式来处理。
具体查看SSE介绍可以查看这篇文章,这里就不重复赘述了,文章地址
- 服务端代码,中间想完善一些功能,代码还没添加,可以当
demo
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
type event struct {
Timestamp int64 `json:"timestamp"`
Message string `json:"message"`
}
func main() {
broker := NewBroker()
go func() {
for {
time.Sleep(time.Second * 2)
now := time.Now()
evt := event{
Timestamp: now.Unix(),
Message: fmt.Sprintf("Hello at %s", now.Format("2006/01/02 15:04:05")),
}
evtBytes, err := json.Marshal(evt)
if err != nil {
log.Println(err)
continue
}
broker.Notifier <- evtBytes
}
}()
time.LoadLocation("Asia/Shanghai")
http.HandleFunc("/", broker.ServeHTTP)
http.ListenAndServe(":8080", nil)
}
type Broker struct {
Notifier chan []byte
newClients chan chan []byte
closingClients chan chan []byte
clients map[chan []byte]bool
}
func (b *Broker) ServeHTTP(w http.ResponseWriter, r *http.Request){
w.Header().Add("Content-Type", "text/event-stream")
w.Header().Add("Cache-Control", "no-cache")
w.Header().Add("Connection", "keep-alive")
w.Header().Add("Access-Control-Allow-Origin", "*")
messageChan := make(chan []byte)
messageChan <- []byte(r.RemoteAddr)
b.newClients <- messageChan
ctx := r.Context()
/// 这是之前使用 notifier来监听连接断开,发现该接口已经弃用了(未删除,还可以正常使用,但不建议),新文档建议使用context
// notify := w.(http.CloseNotifier).CloseNotify()
//go func(req chan []byte) {
// if res := <-notify; res {
// // req断开连接
// b.closingClients <- req
// }
//}(messageChan)
for {
select {
case <-ctx.Done():
b.closingClients <- messageChan
fmt.Println("客户端断开连接")
default:
fmt.Fprintf(w, "data: %s\n\n", <-messageChan)
w.(http.Flusher).Flush()
}
}
}
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
log.Printf("新客户端连接+1,当前客户端数量:%d \n", len(b.clients))
case s := <-b.closingClients:
delete(b.clients, s)
log.Printf("客户端离线,当前客户端数量:%d \n", len(b.clients))
case event := <-b.Notifier:
for clientMessageChan := range b.clients {
clientMessageChan <- event
}
}
}
}
- 客户端
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSe</title>
</head>
<body>
<div id="message"></div>
<script>
const messageElement = document.getElementById('message')
const eventSource = new EventSource("http://localhost:8080/")
eventSource.onopen = () => {
messageElement.innerHTML += `SSE 连接成功,状态${eventSource.readyState}<br />`
}
eventSource.onerror = () => {
messageElement.innerHTML += `SSE 连接错误,状态${eventSource.readyState}<br />`
}
eventSource.onmessage = msg => {
messageElement.innerHTML += `${msg.data} <br />`
}
</script>
</body>
</html>