mirror of
https://github.com/tiennm99/gomoku.git
synced 2026-05-24 00:24:30 +00:00
9e14f3a9b3
- Add server/network/{server,codec,reader,writer,dispatch,handlers_stateless}.go
- Single /gomoku WS endpoint; binary protobuf frames only (no JSON/base64)
- Per-connection reader + writer goroutines; write mutex serialises WS writes
- Dispatch type-switch: stateless (heartbeat, set_nickname, get_rooms,
set_client_info, client_exit) inline; stateful requests → player.CmdCh
- Add Player.SendCh, CmdCh, LastHeartbeat, ClientVersion, Send() to database.Player
- Rewire main.go: NewServer(":1999").Serve() + database.StartCleanup()
- Empty wss.go (old shim superseded by server.go)
71 lines
2.0 KiB
Go
71 lines
2.0 KiB
Go
package network
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
|
|
"github.com/tiennm99/gomoku/server/database"
|
|
"github.com/tiennm99/gomoku/server/pkg/log"
|
|
)
|
|
|
|
const (
|
|
// maxMessageSize caps inbound WS frame size at 4 KB.
|
|
// Protobuf game messages are tiny; anything larger is malicious or buggy.
|
|
maxMessageSize = 4 * 1024
|
|
|
|
// readDeadline is the maximum time to wait for a frame before considering
|
|
// the client dead. Clients send heartbeats every ~50 s; 90 s gives headroom.
|
|
readDeadline = 90 * time.Second
|
|
)
|
|
|
|
// readLoop blocks until the connection closes or an unrecoverable read error occurs.
|
|
// It decodes each binary WS frame into a Request and dispatches it.
|
|
// On exit it closes the player's CmdCh and removes them from the store.
|
|
func readLoop(conn *websocket.Conn, player *database.Player) {
|
|
conn.SetReadLimit(maxMessageSize)
|
|
_ = conn.SetReadDeadline(time.Now().Add(readDeadline))
|
|
|
|
// Extend deadline on each ping/pong so the 90 s window is per-message, not absolute.
|
|
conn.SetPongHandler(func(string) error {
|
|
return conn.SetReadDeadline(time.Now().Add(readDeadline))
|
|
})
|
|
|
|
defer func() {
|
|
// Signal state machine and writer goroutine that this player is gone.
|
|
if player.CmdCh != nil {
|
|
close(player.CmdCh)
|
|
}
|
|
database.RemovePlayer(player.ID)
|
|
log.Infof("[reader] player %d disconnected\n", player.ID)
|
|
}()
|
|
|
|
for {
|
|
msgType, data, err := conn.ReadMessage()
|
|
if err != nil {
|
|
if websocket.IsUnexpectedCloseError(err,
|
|
websocket.CloseGoingAway,
|
|
websocket.CloseNormalClosure,
|
|
websocket.CloseNoStatusReceived) {
|
|
log.Errorf("[reader] player %d read error: %v\n", player.ID, err)
|
|
}
|
|
return
|
|
}
|
|
if msgType != websocket.BinaryMessage {
|
|
log.Errorf("[reader] player %d: unexpected message type %d, dropping\n", player.ID, msgType)
|
|
continue
|
|
}
|
|
|
|
// Reset deadline on any incoming frame (heartbeats count).
|
|
_ = conn.SetReadDeadline(time.Now().Add(readDeadline))
|
|
|
|
req, err := DecodeRequest(data)
|
|
if err != nil {
|
|
log.Errorf("[reader] player %d decode error: %v\n", player.ID, err)
|
|
continue
|
|
}
|
|
|
|
Dispatch(player, req)
|
|
}
|
|
}
|