(ctx context.Context, network, address string)
| 308 | } |
| 309 | |
| 310 | func (s *socks5Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { |
| 311 | start := time.Now() |
| 312 | atomic.AddInt64(&s.stats.TotalConnections, 1) |
| 313 | |
| 314 | // 创建一个带超时的上下文 |
| 315 | dialCtx, cancel := context.WithTimeout(ctx, s.config.Timeout) |
| 316 | defer cancel() |
| 317 | |
| 318 | // 使用goroutine处理拨号,以支持取消 |
| 319 | connChan := make(chan struct { |
| 320 | conn net.Conn |
| 321 | err error |
| 322 | }, 1) |
| 323 | |
| 324 | go func() { |
| 325 | conn, err := s.dialer.Dial(network, address) |
| 326 | select { |
| 327 | case <-dialCtx.Done(): |
| 328 | if conn != nil { |
| 329 | _ = conn.Close() // context取消路径,Close错误可忽略 |
| 330 | } |
| 331 | case connChan <- struct { |
| 332 | conn net.Conn |
| 333 | err error |
| 334 | }{conn, err}: |
| 335 | } |
| 336 | }() |
| 337 | |
| 338 | select { |
| 339 | case <-dialCtx.Done(): |
| 340 | atomic.AddInt64(&s.stats.FailedConnections, 1) |
| 341 | s.stats.mu.Lock() |
| 342 | s.stats.LastError = dialCtx.Err().Error() |
| 343 | s.stats.mu.Unlock() |
| 344 | return nil, NewProxyError(ErrTypeTimeout, ErrMsgSOCKS5ConnTimeout, ErrCodeSOCKS5ConnTimeout, dialCtx.Err()) |
| 345 | case result := <-connChan: |
| 346 | duration := time.Since(start) |
| 347 | |
| 348 | s.stats.mu.Lock() |
| 349 | s.stats.LastConnectTime = start |
| 350 | s.stats.mu.Unlock() |
| 351 | |
| 352 | if result.err != nil { |
| 353 | atomic.AddInt64(&s.stats.FailedConnections, 1) |
| 354 | s.stats.mu.Lock() |
| 355 | s.stats.LastError = result.err.Error() |
| 356 | s.stats.mu.Unlock() |
| 357 | return nil, NewProxyError(ErrTypeConnection, ErrMsgSOCKS5ConnFailed, ErrCodeSOCKS5ConnFailed, result.err) |
| 358 | } |
| 359 | |
| 360 | atomic.AddInt64(&s.stats.ActiveConnections, 1) |
| 361 | s.updateAverageConnectTime(duration) |
| 362 | |
| 363 | return &trackedConn{ |
| 364 | Conn: result.conn, |
| 365 | stats: s.stats, |
| 366 | }, nil |
| 367 | } |
no test coverage detected