package transport import ( "fmt" "net" "sync" "syscall" "omnisocketgo/cmd/internal/latencylog" "omnisocketgo/cmd/internal/protocol" ) // UDPConn 是对 UDP 连接的轻量封装。 // server 侧:共享同一个 net.UDPConn,Send 时通过 peerAddr 指定对端地址。 // peer 侧:独立的 net.UDPConn,已通过 Dial 连接到 server,Send 直接写即可。 type UDPConn struct { conn *net.UDPConn peerAddr *net.UDPAddr // server 侧为对端地址;peer 侧为 nil(连接模式下直接 Write) raw syscall.RawConn // 底层 syscall 句柄,用于 Linux socket timestamping logger latencylog.Logger txTimestampDebugLogger TXTimestampDebugLogger nodeRole string // 日志中记录的节点角色,例如 "server" 或 "peer" nodeID string // 日志中记录的节点 ID writeMu sync.Mutex // 保护 Send 的互斥锁 closeOnce sync.Once closeErr error } // UDPOption 用于为 UDPConn 注入可选行为。 type UDPOption func(*UDPConn) // WithUDPLogger 为 UDP 连接注入业务消息日志上下文。 func WithUDPLogger(logger latencylog.Logger, nodeRole, nodeID string) UDPOption { return func(conn *UDPConn) { conn.logger = logger conn.nodeRole = nodeRole conn.nodeID = nodeID } } // WithUDPTXTimestampDebugLogger 为 UDP 连接注入可选的 TX errqueue 调试日志器。 func WithUDPTXTimestampDebugLogger(logger TXTimestampDebugLogger) UDPOption { return func(conn *UDPConn) { conn.txTimestampDebugLogger = logger } } // NewUDPConn 创建 UDP transport 连接封装。 // peerAddr 为 nil 时表示 peer 侧已连接模式(conn 已 Dial 到 server)。 // peerAddr 非 nil 时表示 server 侧,Send 时需要指定目标地址。 func NewUDPConn(conn *net.UDPConn, peerAddr *net.UDPAddr, opts ...UDPOption) (*UDPConn, error) { udpConn := &UDPConn{ conn: conn, peerAddr: peerAddr, logger: latencylog.NoopLogger{}, } for _, opt := range opts { opt(udpConn) } if udpConn.logger == nil { udpConn.logger = latencylog.NoopLogger{} } if err := udpConn.initUDPLinuxTimestamping(); err != nil { return nil, err } return udpConn, nil } // Send 将一条协议消息编码为 UDP 数据报并发送。 // 多个 goroutine 可以并发调用,内部会串行化写入。 func (c *UDPConn) Send(msg protocol.Message) error { c.writeMu.Lock() defer c.writeMu.Unlock() latencylog.LogMessageEvent(c.logger, c.nodeRole, c.nodeID, latencylog.EventSendHandoffBegin, msg) if err := c.sendMessageLinux(msg); err != nil { return fmt.Errorf("transport: udp send message: %w", err) } latencylog.LogMessageEvent(c.logger, c.nodeRole, c.nodeID, latencylog.EventSendHandoffEnd, msg) return nil } // SendTo 将一条协议消息编码为 UDP 数据报并发送到指定地址。 // 主要用于 server 侧向特定 peer 发送消息。 func (c *UDPConn) SendTo(msg protocol.Message, addr *net.UDPAddr) error { c.writeMu.Lock() defer c.writeMu.Unlock() latencylog.LogMessageEvent(c.logger, c.nodeRole, c.nodeID, latencylog.EventSendHandoffBegin, msg) if err := c.sendMessageToLinux(msg, addr); err != nil { return fmt.Errorf("transport: udp send message to %s: %w", addr, err) } latencylog.LogMessageEvent(c.logger, c.nodeRole, c.nodeID, latencylog.EventSendHandoffEnd, msg) return nil } // Receive 从 UDP 连接读取一条完整协议消息。 // 返回解码后的消息和来源地址(peer 侧来源地址始终为 server 地址)。 func (c *UDPConn) Receive() (protocol.Message, *net.UDPAddr, error) { msg, addr, err := c.receiveMessageLinux() if err != nil { return protocol.Message{}, nil, fmt.Errorf("transport: udp receive message: %w", err) } return msg, addr, nil } // ReceiveLoop 持续从 UDP 连接读取消息并交给 handler 处理。 // handler 的第二个参数是消息来源地址。 func (c *UDPConn) ReceiveLoop(handler func(protocol.Message, *net.UDPAddr) error) error { for { msg, addr, err := c.Receive() if err != nil { return fmt.Errorf("transport: udp receive loop read: %w", err) } if err := handler(msg, addr); err != nil { return fmt.Errorf("transport: udp receive loop handler: %w", err) } } } // Close 关闭底层 UDP 连接,保证重复调用安全。 // 注意:server 侧多个 UDPConn 共享同一个 net.UDPConn 时, // 只应由 UDPHub 负责关闭底层连接,不应通过此方法关闭。 func (c *UDPConn) Close() error { c.closeOnce.Do(func() { c.closeErr = c.conn.Close() }) return c.closeErr }