feat(mpsync): 多人同步 MVP——登录区块流、方块变更广播;修复 netserver 关闭竞态与世界 worker 退出死锁

This commit is contained in:
NianGao Dev
2026-08-15 23:17:34 +08:00
parent 4eaab7f3f9
commit 4167bf287d
6 changed files with 303 additions and 23 deletions

View File

@@ -10,6 +10,7 @@ import (
"fmt"
"io"
"net"
"runtime/debug"
"sync"
"sync/atomic"
"time"
@@ -112,12 +113,13 @@ type Conn struct {
conn net.Conn
srv *Server
send chan []byte
done chan struct{}
closeOnce sync.Once
}
// newConn 创建连接。
func newConn(id uint64, nc net.Conn, s *Server) *Conn {
return &Conn{id: id, conn: nc, srv: s, send: make(chan []byte, 64)}
return &Conn{id: id, conn: nc, srv: s, send: make(chan []byte, 64), done: make(chan struct{})}
}
// ID 连接 ID(服务器分配,多人同步.md §3)。
@@ -186,29 +188,41 @@ func (c *Conn) handle(f netproto.Frame) {
// writeLoop 写循环:channel 驱动,写超时断开(网络协议.md §7)。
func (c *Conn) writeLoop() {
for data := range c.send {
_ = c.conn.SetWriteDeadline(time.Now().Add(readTimeout))
if _, err := c.conn.Write(data); err != nil {
c.close()
for {
select {
case <-c.done:
return
case data := <-c.send:
_ = c.conn.SetWriteDeadline(time.Now().Add(readTimeout))
if _, err := c.conn.Write(data); err != nil {
c.close()
return
}
}
}
}
// Send 发送一帧(非阻塞入队;队满丢弃并告警,防慢连接阻塞服务器)。
// Send 发送一帧(非阻塞入队;连接关闭或队满时丢弃并告警,防慢连接阻塞服务器)。
func (c *Conn) Send(f netproto.Frame) {
select {
case <-c.done:
return
default:
}
select {
case c.send <- netproto.EncodeFrame(f):
case <-c.done:
default:
c.srv.log.Warnf("连接 %d 发送队列已满,丢弃帧 0x%02X", c.id, f.MsgID)
}
}
// close 幂等关闭。
// close 幂等关闭:关闭 done 信号与 socket;不关闭 send(避免与在途 Send 竞争)。
func (c *Conn) close() {
c.closeOnce.Do(func() {
c.srv.log.Debugf("连接 %d 关闭(来源:\n%s)", c.id, debug.Stack())
close(c.done)
_ = c.conn.Close()
close(c.send)
c.srv.mu.Lock()
delete(c.srv.conns, c.id)
c.srv.mu.Unlock()