package main import ( "encoding/json" "log" "net/http" "time" "math/rand" "github.com/gorilla/websocket" ) // Client represents a single WebSocket connection. type Client struct { hub *Hub conn *websocket.Conn send chan []byte } // Hub maintains the set of active clients and broadcasts messages to them. type Hub struct { clients map[*Client]bool broadcast chan []byte register chan *Client unregister chan *Client } func newHub() *Hub { return &Hub{ clients: make(map[*Client]bool), broadcast: make(chan []byte), register: make(chan *Client), unregister: make(chan *Client), } } func (h *Hub) run() { for { select { case c := <-h.register: h.clients[c] = true case c := <-h.unregister: if _, ok := h.clients[c]; ok { delete(h.clients, c) close(c.send) } case message := <-h.broadcast: for c := range h.clients { select { case c.send <- message: default: close(c.send) delete(h.clients, c) } } } } } const ( writeWait = 10 * time.Second pongWait = 60 * time.Second pingPeriod = (pongWait * 9) / 10 ) var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } func (c *Client) writePump() { ticker := time.NewTicker(pingPeriod) defer func() { ticker.Stop() c.conn.Close() }() for { select { case message, ok := <-c.send: c.conn.SetWriteDeadline(time.Now().Add(writeWait)) if !ok { c.conn.WriteMessage(websocket.CloseMessage, []byte{}) return } w, err := c.conn.NextWriter(websocket.TextMessage) if err != nil { return } w.Write(message) if err := w.Close(); err != nil { return } case <-ticker.C: c.conn.SetWriteDeadline(time.Now().Add(writeWait)) if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { return } } } } func (c *Client) readPump() { defer func() { c.hub.unregister <- c c.conn.Close() }() c.conn.SetReadLimit(5120) c.conn.SetReadDeadline(time.Now().Add(pongWait)) c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) for { _, _, err := c.conn.ReadMessage() if err != nil { break } } } func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { ws, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Println("WebSocket upgrade error:", err) return } client := &Client{hub: hub, conn: ws, send: make(chan []byte, 256)} client.hub.register <- client go client.writePump() go client.readPump() } // Stats payload shape type Stats struct { TotalRx int `json:"total_rx"` TotalTx int `json:"total_tx"` PeersOnline int `json:"peers_online"` PeersOffline int `json:"peers_offline"` } // startStatsBroadcast periodically emits mock stats to all connected clients. func startStatsBroadcast(hub *Hub) { rand.Seed(time.Now().UnixNano()) var totalRx, totalTx int ticker := time.NewTicker(5 * time.Second) go func() { for { <-ticker.C totalRx += rand.Intn(120) totalTx += rand.Intn(150) online := rand.Intn(5) + 1 offline := 5 - online s := Stats{TotalRx: totalRx, TotalTx: totalTx, PeersOnline: online, PeersOffline: offline} payload, err := json.Marshal(s) if err != nil { log.Println("Stats marshal error:", err) continue } hub.broadcast <- payload } }() }