Golang实现Socks5代理时远程主机TCP连接超时问题

Golang实现Socks5代理时远程主机TCP连接超时问题 我尝试通过SOCKS5代理向远程主机发送请求,但遇到了超时问题。
看起来我设置的超时时间被忽略了,因为应用程序会卡住2分钟。

我有以下代码:

		dialer, socks_err := proxy.SOCKS5("tcp", proxyAddr,
			nil,
			&net.Dialer {
				Timeout: time.Second * 5,
			},
		)

		if err != nil {
			fmt.Println("can't connect to the proxy:", err)
			return
		}

		connection, err = dialer.Dial("tcp", "google.com:43") // 应用程序在这里卡住,2分钟后出现"i/o timeout"错误

		fmt.Println(err)

看起来我设置的超时仅适用于连接到代理服务器,但不适用于连接到远程主机。

如何为远程主机连接/请求设置超时?
谢谢!


更多关于Golang实现Socks5代理时远程主机TCP连接超时问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang实现Socks5代理时远程主机TCP连接超时问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你遇到的问题确实是因为net.Dialer的超时设置只适用于到SOCKS5代理服务器的初始连接,而不适用于通过代理建立的到远程主机的连接。要解决这个问题,你需要为远程主机连接设置独立的超时控制。

以下是几种解决方案:

方案1:使用context.WithTimeout(推荐)

import (
    "context"
    "net"
    "time"
    "golang.org/x/net/proxy"
)

func main() {
    dialer, err := proxy.SOCKS5("tcp", proxyAddr, nil, &net.Dialer{
        Timeout: 5 * time.Second,
    })
    if err != nil {
        fmt.Println("can't connect to the proxy:", err)
        return
    }

    // 为远程主机连接设置超时
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    var connection net.Conn
    connection, err = dialer.(proxy.ContextDialer).DialContext(ctx, "tcp", "google.com:43")
    if err != nil {
        fmt.Println("connection failed:", err)
        return
    }
    defer connection.Close()
    
    fmt.Println("Connected successfully")
}

方案2:使用net.Dialer包装器

type timeoutDialer struct {
    proxy.Dialer
    timeout time.Duration
}

func (td *timeoutDialer) Dial(network, addr string) (net.Conn, error) {
    ctx, cancel := context.WithTimeout(context.Background(), td.timeout)
    defer cancel()
    
    if contextDialer, ok := td.Dialer.(proxy.ContextDialer); ok {
        return contextDialer.DialContext(ctx, network, addr)
    }
    
    // 如果代理不支持ContextDialer,使用通道实现超时
    result := make(chan struct {
        conn net.Conn
        err  error
    })
    
    go func() {
        conn, err := td.Dialer.Dial(network, addr)
        result <- struct {
            conn net.Conn
            err  error
        }{conn, err}
    }()
    
    select {
    case <-ctx.Done():
        return nil, ctx.Err()
    case res := <-result:
        return res.conn, res.err
    }
}

// 使用包装器
func main() {
    baseDialer, err := proxy.SOCKS5("tcp", proxyAddr, nil, &net.Dialer{
        Timeout: 5 * time.Second,
    })
    if err != nil {
        fmt.Println("can't connect to the proxy:", err)
        return
    }

    timeoutDialer := &timeoutDialer{
        Dialer:  baseDialer,
        timeout: 5 * time.Second,
    }

    connection, err := timeoutDialer.Dial("tcp", "google.com:43")
    if err != nil {
        fmt.Println("connection failed:", err)
        return
    }
    defer connection.Close()
    
    fmt.Println("Connected successfully")
}

方案3:使用goroutine和select实现超时

func dialWithTimeout(dialer proxy.Dialer, network, addr string, timeout time.Duration) (net.Conn, error) {
    result := make(chan struct {
        conn net.Conn
        err  error
    })
    
    go func() {
        conn, err := dialer.Dial(network, addr)
        result <- struct {
            conn net.Conn
            err  error
        }{conn, err}
    }()
    
    select {
    case <-time.After(timeout):
        return nil, &net.OpError{
            Op:  "dial",
            Net: network,
            Err: &timeoutError{},
        }
    case res := <-result:
        return res.conn, res.err
    }
}

type timeoutError struct{}

func (e *timeoutError) Error() string   { return "dial timeout" }
func (e *timeoutError) Timeout() bool   { return true }
func (e *timeoutError) Temporary() bool { return true }

// 使用
func main() {
    dialer, err := proxy.SOCKS5("tcp", proxyAddr, nil, &net.Dialer{
        Timeout: 5 * time.Second,
    })
    if err != nil {
        fmt.Println("can't connect to the proxy:", err)
        return
    }

    connection, err := dialWithTimeout(dialer, "tcp", "google.com:43", 5*time.Second)
    if err != nil {
        fmt.Println("connection failed:", err)
        return
    }
    defer connection.Close()
    
    fmt.Println("Connected successfully")
}

第一种方案是最简洁和推荐的,因为它利用了Go标准库的context机制。确保你的golang.org/x/net/proxy版本支持ContextDialer接口。

回到顶部