乐趣区

关于tcp:TCP-Keepalive与gorpc的tcp连接

TCP Keepalive

tcp 连贯被形象为一个 socket,socket 上增加了 SO_KEEPALIVE 后,该 socket 便能够启用 keepalive。

keepalive 的连贯为长连贯,这样 client 向 server 交互时不必每次都新建连贯,用长连贯进行继续的数据读取和写入即可。

keepalive 的连贯须要定期进行探测,当 client 不再沉闷时,server 端及时的开释该连贯。

tcp keepalive 的参数:

  • tcp_keepalive_time: 单位 s,默认 7200

    • client 与 server 多久没有数据交互,就认为 connection idle,而后开始发动探测。
  • tcp_keepalive_intvl: 单位 s,默认 75

    • 一次探测结束后,期待多久进行下一次探测。
  • tcp_keepalive_probes:单位次数,默认 9

    • 最大探测次数,某个连贯通过 N 次探测后依然不沉闷将被开释。

默认状况下:

  • 2 个小时 (7200s)(tcp_keepalive_time) 没有数据交互,就认为 connection idle;
  • 而后发动 keep-alive 音讯,探测 client 是否存活;
  • 每隔 tcp_keepalive_intvl(75s)发动一次探测,探测 tcp_keepalive_probes(9)次后,将彻底 kill 连贯;

总结来说,1 个 tcp 连贯,要等:7200+75*9=2hour11min 后,才被 kill 掉;

个别生产环境都会配置下面的 3 个参数,目录 /proc/sys/net/ipv4/ 下:

//tcp_keepalive_time 参数
/proc/sys/net/ipv4 # cat tcp_keepalive_time
90
//tcp_keeplive_intv 参数
/proc/sys/net/ipv4# cat tcp_keepalive_intvl
15
//tcp_keepalive_probes 参数
/proc/sys/net/ipv4# cat tcp_keepalive_probes
2

当程序中 socket 未配置 keep-alive 参数时,就应用零碎上配置的参数。

Keepalive: TCP VS HTTP

Http 的 keepalive 用于连贯复用,在同一个连贯上 request-response。

Tcp 的 keepalive 用于保活、心跳。

go-rpc 的 TCP Keepalive

go-rpc 是 golang 自带的 rpc 框架,server 端的代码:

func StartRpc() {tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
    if err != nil {log.Fatalln("addr err:", err)
    }
    listener, err := net.ListenTCP("tcp", tcpAddr)
    if err != nil {log.Fatalln("listen err:", err)
    }
    server := rpc.NewServer()
    server.Register(new(Transfer))
    for {conn, err := listener.AcceptTCP()
        if err != nil {log.Println("accept err:", err)
            continue
        }
        log.Println("accept tcp from:", conn.RemoteAddr())
        go server.ServeCodec(jsonrpc.NewServerCodec(conn))
    }
}

服务端接管 1 个 connection,而后启动 1 个 goroutine 解决该连贯上的 request:

conn, err := listener.AcceptTCP()
go server.ServeCodec(jsonrpc.NewServerCodec(conn))

接管 TCP 连贯时,先配置 TCP 为 keepalive 长连贯,而后再配置 keepalive 参数:

func (ln *TCPListener) accept() (*TCPConn, error) {fd, err := ln.fd.accept()
    if err != nil {return nil, err}
    tc := newTCPConn(fd)
    if ln.lc.KeepAlive >= 0 {setKeepAlive(fd, true)        // 启用 tcp keepalive
        ka := ln.lc.KeepAlive
        if ln.lc.KeepAlive == 0 {ka = defaultTCPKeepAlive    // 默认 keepalive 工夫 =15s}
        setKeepAlivePeriod(fd, ka)
    }
    return tc, nil
}

启用 TCP keepalive:

// 配置 Keepalive 标记
func setKeepAlive(fd *netFD, keepalive bool) error {err := fd.pfd.SetsockoptInt(syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, boolint(keepalive))
    runtime.KeepAlive(fd)
    return wrapSyscallError("setsockopt", err)
}

配置 TCP keepalive 的工夫参数:

  • syscall.TCP_KEEPIDLE: tcp_keepalive_time 参数,配置为 15s;
  • syscall.TCP_KEEPINTVL: tcp_keepalive_intvl 参数,配置为 15s;
  • tcp_keepalive_probes 应用系统配置:2;

总结下来,server 在连贯 15s 没有数据后,发动探测,距离 15s 发动一次探测,探测 2 次后不再沉闷就 kill 连贯,故一个闲暇连贯要等:15+15*2=45s 后被 kill。

func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
    // The kernel expects seconds so round to next highest second.
    secs := int(roundDurationUp(d, time.Second))
    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)
}

syscall 中的调用参数:

// 其中:TCP_KEEPIDLE --> /proc/sys/net/ipv4/tcp_keepalive_time
TCP_KEEPINTVL --> /proc/sys/net/ipv4/tcp_keepalive_intvl
TCP_KEEPCNT --> /proc/sys/net/ipv4/tcp_keepalive_probes

参考

1.https://zhuanlan.zhihu.com/p/…
2.https://tldp.org/HOWTO/TCP-Ke…

退出移动版