Golang实现客户端自动重连服务器的几种方法

Golang实现客户端自动重连服务器的几种方法 我正在尝试编写一个客户端,之后会为 Windows 和 Linux 进行交叉编译。如果连接因任何原因断开,我需要它能自动重连回服务器。我不是 Go 语言专家,所以在此寻求帮助!

到目前为止,我几乎已经实现了。首次启动时,它会每 3 秒尝试连接一次服务器(我使用 ncat 作为服务器),直到成功为止。

└─$ ./netgo 127.0.0.1 9999
连接失败
连接失败
连接失败
连接成功
2021/06/25 10:29:25 已连接到 127.0.0.1:9999

但如果连接断开,我需要它能自动重连回服务器。目前,我只能在客户端收到 RST 后,从客户端输入某种内容时才能让它工作。

└─$ ./netgo 127.0.0.1 9999
连接失败
连接失败
连接失败
连接成功
2021/06/25 10:31:48 已连接到 127.0.0.1:9999
abc
再次失败
连接成功
2021/06/25 10:32:38 已连接到 127.0.0.1:9999

代码如下:

// Socket Client
func (nObj NetObject) RunClient(cmd string) {
    // Try connection

    for {
        conn, err := net.Dial(nObj.Type, nObj.Service)
        fmt.Print("connect")
        if err != nil {
            fmt.Println("fail")
        } else {
            fmt.Println("ok")
            defer conn.Close()
            log.Println("Connected to", conn.RemoteAddr())
            handleConn(conn, cmd)
            if conn != nil {
                fmt.Println("fail again ")
            }
        }
        time.Sleep(5 * time.Second)
    }

}

// Manage connection for different behavior
func handleConn(conn net.Conn, binPath string) {
    if binPath != "" {
        // Execute command and send Standard I/O net.Conn
        cmd := exec.Command(binPath)
        cmd.Stdin = conn
        cmd.Stdout = conn
        cmd.Stderr = conn
        cmd.Run()
    } else {
        // Copy Standard I/O in a net.Conn
        go io.Copy(os.Stderr, conn)
        go io.Copy(os.Stdout, conn)
        io.Copy(conn, os.Stdin)
    }
}

你能帮我修复这段代码吗?那将非常棒!


更多关于Golang实现客户端自动重连服务器的几种方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

除了尝试使用连接,你无法知道连接是否已断开。TCP使用确认包来指示数据何时被接收,因此如果你尝试发送数据但没有收到ACK回复,那么你就知道连接已中断。你可以编写一些代码,持续向服务器发送ping,并在出错时重新连接。

func main() {
    fmt.Println("hello world")
}

更多关于Golang实现客户端自动重连服务器的几种方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


要实现客户端自动重连,关键是要在连接断开后重新进入连接循环。以下是修复后的代码:

func (nObj NetObject) RunClient(cmd string) {
    for {
        conn, err := net.Dial(nObj.Type, nObj.Service)
        if err != nil {
            fmt.Println("连接失败")
            time.Sleep(3 * time.Second)
            continue
        }
        
        fmt.Println("连接成功")
        log.Println("已连接到", conn.RemoteAddr())
        
        // 使用WaitGroup等待连接处理完成
        var wg sync.WaitGroup
        wg.Add(1)
        
        go func() {
            defer wg.Done()
            handleConn(conn, cmd)
        }()
        
        // 等待连接处理完成(即连接断开)
        wg.Wait()
        
        fmt.Println("连接断开,尝试重连...")
        conn.Close()
        time.Sleep(3 * time.Second)
    }
}

func handleConn(conn net.Conn, binPath string) {
    defer conn.Close()
    
    if binPath != "" {
        cmd := exec.Command(binPath)
        cmd.Stdin = conn
        cmd.Stdout = conn
        cmd.Stderr = conn
        cmd.Run()
    } else {
        // 使用context来处理连接超时
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel()
        
        // 监控连接状态
        go func() {
            buf := make([]byte, 1)
            for {
                _, err := conn.Read(buf)
                if err != nil {
                    cancel()
                    return
                }
            }
        }()
        
        // 使用goroutine处理双向数据流
        done := make(chan bool, 2)
        
        go func() {
            io.Copy(os.Stderr, conn)
            done <- true
        }()
        
        go func() {
            io.Copy(os.Stdout, conn)
            done <- true
        }()
        
        go func() {
            io.Copy(conn, os.Stdin)
            done <- true
        }()
        
        // 等待连接断开或超时
        select {
        case <-ctx.Done():
            return
        case <-done:
            return
        }
    }
}

另一个更简洁的实现方式,使用心跳检测:

func (nObj NetObject) RunClient(cmd string) {
    var reconnectDelay = 3 * time.Second
    
    for {
        conn, err := net.Dial(nObj.Type, nObj.Service)
        if err != nil {
            fmt.Println("连接失败")
            time.Sleep(reconnectDelay)
            continue
        }
        
        fmt.Println("连接成功")
        log.Println("已连接到", conn.RemoteAddr())
        
        // 设置连接超时
        conn.SetDeadline(time.Time{})
        
        // 处理连接
        if err := handleConnWithRetry(conn, cmd); err != nil {
            fmt.Printf("连接错误: %v\n", err)
        }
        
        conn.Close()
        fmt.Println("尝试重连...")
        time.Sleep(reconnectDelay)
    }
}

func handleConnWithRetry(conn net.Conn, binPath string) error {
    if binPath != "" {
        return handleCommandConn(conn, binPath)
    }
    return handleInteractiveConn(conn)
}

func handleInteractiveConn(conn net.Conn) error {
    errChan := make(chan error, 3)
    
    // 启动数据转发
    go func() {
        _, err := io.Copy(os.Stderr, conn)
        errChan <- err
    }()
    
    go func() {
        _, err := io.Copy(os.Stdout, conn)
        errChan <- err
    }()
    
    go func() {
        _, err := io.Copy(conn, os.Stdin)
        errChan <- err
    }()
    
    // 等待任意一个copy操作出错
    return <-errChan
}

如果需要更精确的连接状态监控,可以添加心跳机制:

func maintainConnection(conn net.Conn) bool {
    // 设置读超时检测连接是否存活
    conn.SetReadDeadline(time.Now().Add(10 * time.Second))
    
    buf := make([]byte, 1)
    _, err := conn.Read(buf)
    if err != nil {
        if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
            // 只是超时,连接可能仍然有效
            conn.SetReadDeadline(time.Time{})
            return true
        }
        // 真实错误,连接已断开
        return false
    }
    
    conn.SetReadDeadline(time.Time{})
    return true
}

这些实现都能在连接断开后自动重连,主要改进包括:

  1. 移除了defer conn.Close()的错误位置
  2. 使用WaitGroup或channel等待连接处理完成
  3. 连接断开后自动回到外层循环重新连接
  4. 添加了适当的错误处理和资源清理
回到顶部