package connect import ( "context" "fmt" "net" "time" "go.uber.org/zap" ) // DirectFactory Direct-UDP 直连工厂(Layer 1) type DirectFactory struct { stunServers []string logger *zap.Logger } // NewDirectFactory 创建 Direct-UDP 工厂 func NewDirectFactory(stunServers []string, logger *zap.Logger) *DirectFactory { return &DirectFactory{ stunServers: stunServers, logger: logger, } } // Layer 返回传输层类型 func (f *DirectFactory) Layer() Layer { return LayerDirectUDP } // Name 返回传输方式名称 func (f *DirectFactory) Name() string { return "Direct-UDP" } // Dial 建立 Direct-UDP 直连 func (f *DirectFactory) Dial(ctx context.Context, config *DialConfig) (net.Conn, error) { f.logger.Info("开始建立 Direct-UDP 直连", zap.String("peer_id", config.PeerID)) servers := f.stunServers if len(servers) == 0 { servers = config.STUNServers } if len(servers) == 0 { f.logger.Warn("未配置 STUN 服务器列表,仅尝试内部 P2P 打洞") } var candidates []string if len(servers) > 0 { // 1. 创建 STUN 客户端收集候选地址 stun := NewSTUNClient(servers, f.logger) candidates = stun.CollectCandidates() } if len(candidates) == 0 { f.logger.Warn("未能收集到任何 STUN 候选地址,回退至 PeerID") // As a fallback, maybe PeerID contains IP:PORT candidates = append(candidates, config.PeerID) } f.logger.Info("STUN 候选地址收集完成", zap.Strings("candidates", candidates)) // 2. 实际 P2P 连接尝试 f.logger.Warn("当前尝试所有候选地址...") dialer := &net.Dialer{ Timeout: 5 * time.Second, } // 建立 UDP 连接并返回最近可用的 var lastErr error for _, candidate := range candidates { if candidate == "" { continue } conn, err := dialer.DialContext(ctx, "udp", candidate) if err == nil { f.logger.Info("Direct-UDP 直连建立成功", zap.String("peer_id", config.PeerID), zap.String("remote_addr", conn.RemoteAddr().String())) return conn, nil } f.logger.Warn("候选地址连接失败", zap.String("candidate", candidate), zap.Error(err)) lastErr = err } return nil, fmt.Errorf("所有候选地址 UDP 连接均失败,最后错误:%w", lastErr) }