You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go中为SSH TCP连接设置空闲超时遇连接关闭问题求助

解决Go语言TCP转SSH连接空闲超时导致SSH直接关闭的问题

问题根源

将自定义Conn移到外部后出现SSH连接直接关闭,大概率是两个原因:一是自定义Conn没有完整实现net.Conn接口,导致SSH握手或传输时出现异常;二是超时逻辑在SSH握手阶段就触发,直接中断了连接建立过程。

具体解决步骤

1. 完整实现带超时的自定义Conn

SSH的NewServerConn依赖完整的net.Conn接口实现,仅重写Read方法远远不够,必须透传所有接口方法,同时在每次读写操作后重置超时计时器,确保空闲计时只针对无数据传输的阶段。示例代码:

import (
    "net"
    "sync"
    "time"
)

type TimeoutConn struct {
    net.Conn
    timeout time.Duration
    mu      sync.Mutex
    timer   *time.Timer
}

func NewTimeoutConn(conn net.Conn, timeout time.Duration) *TimeoutConn {
    tc := &TimeoutConn{
        Conn:    conn,
        timeout: timeout,
    }
    tc.resetTimer()
    return tc
}

// 重置超时计时器,加锁保证并发安全
func (tc *TimeoutConn) resetTimer() {
    tc.mu.Lock()
    defer tc.mu.Unlock()
    if tc.timer != nil {
        tc.timer.Stop()
    }
    tc.timer = time.AfterFunc(tc.timeout, func() {
        _ = tc.Conn.Close()
    })
}

// 重写Read,读取成功后重置超时
func (tc *TimeoutConn) Read(b []byte) (n int, err error) {
    n, err = tc.Conn.Read(b)
    if err == nil {
        tc.resetTimer()
    }
    return
}

// 重写Write,写入成功后重置超时
func (tc *TimeoutConn) Write(b []byte) (n int, err error) {
    n, err = tc.Conn.Write(b)
    if err == nil {
        tc.resetTimer()
    }
    return
}

// 重写Close,清理计时器避免资源泄漏
func (tc *TimeoutConn) Close() error {
    tc.mu.Lock()
    defer tc.mu.Unlock()
    if tc.timer != nil {
        tc.timer.Stop()
    }
    return tc.Conn.Close()
}

2. 调整超时启用时机,避开SSH握手阶段

如果在SSH握手(ssh.NewServerConn)过程中就启用超时,很可能因为握手耗时超过阈值导致连接被中断。正确的做法是先完成SSH握手,再为已建立的连接添加超时逻辑:

func handleTCPConn(tcpConn net.Conn) {
    defer tcpConn.Close()

    // 先完成SSH握手,此时不启用超时
    sshConn, chans, reqs, err := ssh.NewServerConn(tcpConn, yourSSHConfig)
    if err != nil {
        log.Printf("SSH handshake failed: %v", err)
        return
    }
    defer sshConn.Close()

    // 握手完成后,包装SSH连接为带超时的Conn
    timeoutConn := NewTimeoutConn(sshConn, 20*time.Second)

    // 处理SSH请求和通道
    go ssh.DiscardRequests(reqs)
    for newChan := range chans {
        if newChan.ChannelType() != "session" {
            newChan.Reject(ssh.UnknownChannelType, "only session channels are supported")
            continue
        }
        chan_, reqs, err := newChan.Accept()
        if err != nil {
            log.Printf("Accept channel failed: %v", err)
            continue
        }
        go handleSSHSession(chan_, reqs, timeoutConn)
    }
}

3. 排查并发安全问题

自定义Conn中的计时器操作必须加锁保护,否则多goroutine同时读写时,可能出现计时器重复Stop/重置的异常,导致连接被误关闭。上面的示例已经通过sync.Mutex保证了计时器操作的原子性。

4. 验证超时逻辑

测试时可以:

  • 建立SSH连接后保持空闲20秒,确认连接被正常关闭
  • 在连接活跃时持续发送数据,确认超时计时器被正确重置,不会误关闭连接

内容的提问来源于stack exchange,提问作者Sanjay Kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 13:15:40