5

TCP Keepalive与go-rpc的tcp连接

 2 years ago
source link: https://segmentfault.com/a/1190000040885321
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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...


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK