Несколько WebSocket-соединений в Go
Один HTTP-сервер может держать много каналов одновременно. Если после перевода запроса долго читать сообщения в том же потоке, следующий клиент будет ждать, пока не освободится обработчик пути.
Поэтому каждое новое соединение часто
кладут в карту под sync.Mutex,
чтобы не потерять указатель на канал,
а длинную работу выносят в горутину.
Перед записью в карту замок берут,
после изменения отпускают; горутины
и замок уже знакомы из раздела про
синхронизацию.
Сервер добавляет каждый канал в карту и читает одно сообщение в отдельной ветке:
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) remove(conn *websocket.Conn) {
h.mu.Lock()
delete(h.conns, conn)
h.mu.Unlock()
}
func handleClient(h *hub, conn *websocket.Conn) {
defer conn.Close()
defer h.remove(conn)
_, msg, err := conn.ReadMessage()
if err != nil {
return
}
log.Println("got", string(msg))
}
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 handleClient(h, conn)
})
log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}
Карта хранит только активные каналы. Удаление в отложенном вызове обработчика не дает копиться закрытым соединениям, если клиент оборвет связь.
Тот же прием карты без чтения сообщения, только регистрация и запуск ветки:
package main
import (
"log"
"net/http"
"sync"
"github.com/gorilla/websocket"
)
var (
mu sync.Mutex
conns = map[*websocket.Conn]struct{}{}
)
func track(conn *websocket.Conn) {
mu.Lock()
conns[conn] = struct{}{}
mu.Unlock()
go func(c *websocket.Conn) {
defer c.Close()
defer func() {
mu.Lock()
delete(conns, c)
mu.Unlock()
}()
for {
if _, _, err := c.ReadMessage(); err != nil {
return
}
}
}(conn)
}
func main() {
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
}
track(conn)
})
log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}
Дана карта и замок:
var (
mu sync.Mutex
conns = map[*websocket.Conn]struct{}{}
)
Напишите функцию, которая под замком
добавляет conn в conns
и запускает горутину с отложенным
удалением из карты.
Дан фрагмент обработчика пути:
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
handleClient(conn)
Скажите, чем такой вызов хуже для второго клиента, пока первый еще читает сообщения.
Скажите, зачем карту клиентов защищают замком, если добавление и удаление идут из разных горутин.