Рассылка по WebSocket в Go
Когда несколько клиентов уже записаны в общую карту, сервер может разослать одну и ту же фразу каждому активному каналу. Обход делают под тем же замком, что и при добавлении, чтобы карта не менялась посередине цикла.
На каждом элементе вызывают отправку текстового кадра с общим срезом байтов. Ошибку записи в один канал обычно логируют и продолжают обход: остальные клиенты все равно должны получить сообщение.
Хранилище с методом рассылки и сервер, который после перевода кладет клиента в карту:
package main
import (
"log"
"net/http"
"sync"
"github.com/gorilla/websocket"
)
type hub struct {
mu sync.Mutex
conns map[*websocket.Conn]struct{}
}
func (h *hub) add(conn *websocket.Conn) {
h.mu.Lock()
h.conns[conn] = struct{}{}
h.mu.Unlock()
}
func (h *hub) broadcast(text []byte) {
h.mu.Lock()
defer h.mu.Unlock()
for conn := range h.conns {
err := conn.WriteMessage(websocket.TextMessage, text)
if err != nil {
log.Println(err)
}
}
}
func main() {
h := &hub{conns: make(map[*websocket.Conn]struct{})}
upgrader := websocket.Upgrader{}
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
h.add(conn)
go func(c *websocket.Conn) {
defer c.Close()
_, _, err := c.ReadMessage()
if err != nil {
return
}
}(conn)
})
go func() {
h.broadcast([]byte("news: server up"))
}()
log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}
В учебном файле рассылку запускают отдельной горутиной; в реальной программе ее вызывают, когда пришло событие или прочитали сообщение от одного клиента.
Тот же обход карты без HTTP-регистрации, если карта уже заполнена:
package main
import (
"log"
"sync"
"github.com/gorilla/websocket"
)
func broadcastAll(mu *sync.Mutex, conns map[*websocket.Conn]struct{}, text []byte) {
mu.Lock()
defer mu.Unlock()
for conn := range conns {
err := conn.WriteMessage(websocket.TextMessage, text)
if err != nil {
log.Println(err)
}
}
}
func main() {
var mu sync.Mutex
conns := make(map[*websocket.Conn]struct{})
broadcastAll(&mu, conns, []byte("hello all"))
}
Дано сообщение:
text := []byte("alert: done")
Напишите функцию, которая под замком
обходит карту conns и отправляет
text каждому каналу как текстовый
кадр.
Дан фрагмент рассылки:
for conn := range h.conns {
conn.WriteMessage(websocket.TextMessage, text)
}
Скажите, какую гонку дает такой обход без замка, если другая горутина в это время удаляет клиента из карты.
Скажите, почему при ошибке отправки одному клиенту цикл обычно не прерывают сразу для всех остальных.