【问题标题】:Keep-alive: dead peers detectionKeep-alive:死节点检测
【发布时间】:2019-06-14 08:15:38
【问题描述】:

我在 macOS localhost 上运行用 Go (1.12) 编写的客户端和套接字服务器。

服务器在 net.TCPConn 上设置 SetKeepAlive 和 SetKeepAlivePeriod。
客户端发送一个数据包然后关闭连接(FIN)或客户端突然终止。

Tcpdump 显示即使在客户端关闭连接后,服务器也会继续发送 keep-alive 探测。
它不应该检测到对等体“已死”并关闭连接吗?

这个问题很笼统,如果我缺少一些基本知识,请随时澄清。

package main

import (
    "flag"
    "fmt"
    "net"
    "os"
    "time"
)

func main() {
    var client bool
    flag.BoolVar(&client, "client", false, "")
    flag.Parse()

    if client {
        fmt.Println("Client mode")
        conn, err := net.Dial("tcp", "127.0.0.1:12345")
        checkErr("Dial", err)

        written, err := conn.Write([]byte("howdy"))
        checkErr("Write", err)

        fmt.Printf("Written: %v\n", written)
        fmt.Println("Holding conn")

        time.Sleep(60 * time.Second)

        err = conn.Close()
        checkErr("Close", err)

        fmt.Println("Closed conn")

        return
    }

    fmt.Println("Server mode")
    l, err := net.Listen("tcp", "127.0.0.1:12345")
    checkErr("listen", err)
    defer l.Close()

    for {
        c, err := l.Accept()
        checkErr("accept", err)
        defer c.Close()

        tcpConn := c.(*net.TCPConn)
        err = tcpConn.SetKeepAlive(true)
        checkErr("SetKeepAlive", err)
        err = tcpConn.SetKeepAlivePeriod(5 * time.Second)
        checkErr("SetKeepAlivePeriod", err)

        b := make([]byte, 1024)

        n, err := c.Read(b)
        checkErr("read", err)

        fmt.Printf("Received: %v\n", string(b[:n]))
    }
}

func checkErr(location string, err error) {
    if err != nil {
        fmt.Printf("%v: %v\n", location, err)
        os.Exit(-1)
    }
}

【问题讨论】:

  • 请分享一些代码,向我们展示您如何打开和关闭连接以及您的服务器是如何配置的。
  • 您永远不会关闭服务器端的连接。 defer c.Close() 将在 main() 返回时运行。
  • @surlac 那你还有什么问题?连接没有关闭,因为您没有关闭它。 “1)它一直发送探针的原因是什么? --- 你没有关闭连接,所以它是打开的并且发送了 keepalive 数据包。 “2)如何在 Go 级别正确终止来自客户端的连接?” --- 双方都应该关闭连接。
  • 因为 tcp 就是这样工作的:来自客户端的FIN 表示“我不会发送任何东西但仍然可以接收”
  • @surlac “这与保持活动的目的相矛盾”——事实并非如此。你观察到的东西“按设计”工作。

标签: go keep-alive tcp-keepalive


【解决方案1】:

对该问题的回答:

仅当您需要打开连接但空闲时才需要发送 keepalive。在这种情况下,存在连接断开的风险,因此保持活动状态将尝试检测断开的连接。

如果您在服务器端使用适当的 con.Close() 关闭了连接,则不会触发保持活动(您确实将其推迟到主函数的末尾)。

如果您测试您的服务器代码,它将在您设置的超时后开始发送保持活动状态

您注意到,只有在所有保持活动证明(内核默认为 9)和证明之间的时间(8x)之后,您会在服务器端读取 io.EOF 错误(是的,服务器停止发送)!

目前,GO 实现在 Linux 和 OSX 上是相同的,它将 TCP_KEEPINTVLTCP_KEEPIDLE 设置为您传递给 setKeepAlivePeriod 函数的值,因此,行为将取决于内核版本。

func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
    // The kernel expects seconds so round to next highest second.
    d += (time.Second - time.Nanosecond)
    secs := int(d.Seconds())
    if err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, secs); err != nil {
        return wrapSyscallError("setsockopt", err)
    }
    err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, secs)
    runtime.KeepAlive(fd)
    return wrapSyscallError("setsockopt", err)
}

有一个自 2014 年以来向provide a way to set keepalive time and interval separately 提出的请求。

一些参考资料:

【讨论】:

  • +1。欣赏答案。您如何检测到它发送了 8 次?它是否在c.Read(b) 上返回 io.EOF?我在 debian 中运行,它在 5 秒空闲后以 5 秒的间隔发送了 15 次探测,然后停留在那里,服务器没有收到 EOF。 macOS 上的类似行为 - 27 次,间隔 5 秒,空闲 5 秒。试图了解我在做什么不同。谢谢。
  • 是的,它在 c.Read(b) 上返回 io.EOF。 keepalive 证明的数量在内核级别设置。如果你输入 sudo sysctl -A | grep keep 你可以读取值,如果你查看 go lang source code 你可以看到实现和使用的参数。
  • 我浏览了您添加的文章。仍然想知道为什么 Read 在我的情况下从不返回 EOF,但我看到 FIN 返回到服务器。在您的情况下,我看到明确终止连接的唯一可能原因 - 有人正在发送RST(例如代理)。
  • @surlac 这也是 TCP 设计的:要关闭连接双方必须 FIN + ACK,或者任何一方都可以 RST。
  • @surlac 另一件值得一提的事情:您只从连接中读取 ONCE,所以如果您没有阅读任何内容,也不会奇怪在任何地方都看不到 io.EOF
猜你喜欢
  • 2016-09-07
  • 1970-01-01
  • 2014-03-07
  • 2023-03-07
  • 2021-07-20
  • 1970-01-01
  • 2018-06-08
  • 2013-03-25
  • 2014-01-22
相关资源
最近更新 更多