使用 ssh 包透明地处理此问题的一种方法是通过自定义创建具有空闲超时的连接net.Conn
它为您设定了最后期限。但是,这会导致后台读取连接超时,因此我们需要使用 ssh keepalives 来保持连接打开。根据您的使用情况,只需使用 ssh keepalives 作为死连接的警报就足够了。
// Conn wraps a net.Conn, and sets a deadline for every read
// and write operation.
type Conn struct {
net.Conn
ReadTimeout time.Duration
WriteTimeout time.Duration
}
func (c *Conn) Read(b []byte) (int, error) {
err := c.Conn.SetReadDeadline(time.Now().Add(c.ReadTimeout))
if err != nil {
return 0, err
}
return c.Conn.Read(b)
}
func (c *Conn) Write(b []byte) (int, error) {
err := c.Conn.SetWriteDeadline(time.Now().Add(c.WriteTimeout))
if err != nil {
return 0, err
}
return c.Conn.Write(b)
}
然后您可以使用net.DialTimeout
or a net.Dialer
要获得连接,请将其包装在您的Conn
超时,并将其传递到ssh.NewClientConn
.
func SSHDialTimeout(network, addr string, config *ssh.ClientConfig, timeout time.Duration) (*ssh.Client, error) {
conn, err := net.DialTimeout(network, addr, timeout)
if err != nil {
return nil, err
}
timeoutConn := &Conn{conn, timeout, timeout}
c, chans, reqs, err := ssh.NewClientConn(timeoutConn, addr, config)
if err != nil {
return nil, err
}
client := ssh.NewClient(c, chans, reqs)
// this sends keepalive packets every 2 seconds
// there's no useful response from these, so we can just abort if there's an error
go func() {
t := time.NewTicker(2 * time.Second)
defer t.Stop()
for range t.C {
_, _, err := client.Conn.SendRequest("[email protected] /cdn-cgi/l/email-protection", true, nil)
if err != nil {
return
}
}
}()
return client, nil
}