package main import ( "encoding/json" "fmt" "log" "time" "github.com/gorilla/websocket" ) const ( writeWait = 10 * time.Second // Time allowed to write a message to the peer. pongWait = 60 * time.Second // Time allowed to read the next pong message from the peer. pingPeriod = (pongWait * 9) / 10 // Send pings to peer with this period. Must be less than pongWait. maxMessageSize = 512 // Maximum message size allowed from peer. ) var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } type Clienter interface { fmt.Stringer getClient() *Client handleMessage(env GameMessage) error finish() } type Client struct { game *Game conn *websocket.Conn outQueue chan []byte } func newClient(g *Game, conn *websocket.Conn) Client { return Client{g, conn, make(chan []byte, 256)} } func (c *Client) send(data []byte) bool { select { case c.outQueue <- data: return true default: close(c.outQueue) return false } } func (c *Client) closeQueue() { close(c.outQueue) } func handleError(c Clienter, err error) { log.Printf("%s error: %v", c, err) errmsg := makeMessage("error", fmt.Sprintf("%v", err)) c.getClient().send(errmsg) } func readPump(c Clienter) { client := c.getClient() defer func() { c.finish() client.conn.Close() }() client.conn.SetReadLimit(maxMessageSize) client.conn.SetReadDeadline(time.Now().Add(pongWait)) client.conn.SetPongHandler(func(string) error { client.conn.SetReadDeadline(time.Now().Add(pongWait)) return nil }) for { _, data, err := client.conn.ReadMessage() if err != nil { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { log.Printf("error: %v", err) } break } var envelope GameMessage if err := json.Unmarshal(data, &envelope); err != nil { handleError(c, fmt.Errorf("Unmarshal: %w", err)) } if err := c.handleMessage(envelope); err != nil { handleError(c, err) } } } func writePump(c Clienter) { ticker := time.NewTicker(pingPeriod) client := c.getClient() defer func() { ticker.Stop() client.conn.Close() }() for { select { case message, ok := <-client.outQueue: client.conn.SetWriteDeadline(time.Now().Add(writeWait)) if !ok { // The game closed the channel. client.conn.WriteMessage(websocket.CloseMessage, []byte{}) return } w, err := client.conn.NextWriter(websocket.TextMessage) if err != nil { return } w.Write(message) if err := w.Close(); err != nil { return } case <-ticker.C: client.conn.SetWriteDeadline(time.Now().Add(writeWait)) if err := client.conn.WriteMessage(websocket.PingMessage, nil); err != nil { return } } } }