网络编程知识

@(常识)

TCP Keepalive

长连接的环境下,人们一般使用业务层面或上层应用层协议(诸如MQTT,SOCKET.IO等)里面定义和使用。
一旦有热数据需要传递,若此时连接已经被中介设备断开,应用程序没有及时感知的话,那么就会导致在一个无效的数据链路层面发送业务数据,结果就是发送失败。
image.png
无论是因为客户端意外断电、死机、崩溃、重启,还是中间路由网络无故断开、NAT超时等,服务器端要做到快速感知失败,减少无效链接操作。
image.png

协议解读

下面协议解读,基于RFC1122#TCP Keep-Alives

  • TCP Keepalive虽不是标准规范,但操作系统一旦实现,默认情况下须为关闭,可以被上层应用开启和关闭。
  • TCP Keepalive必须在没有任何数据(包括ACK包)接收之后的周期内才会被发送,允许配置,默认值不能够小于2个小时
  • 不包含数据的ACK段在被TCP发送时没有可靠性保证,意即一旦发送,不确保一定发送成功。系统实现不能对任何特定探针包作死连接对待
  • 规范建议keepalive保活包不应该包含数据,但也可以包含1个无意义的字节,比如0x0。
  • SEG.SEQ = SND.NXT-1,即TCP保活探测报文序列号将前一个TCP报文序列号减1。SND.NXT = RCV.NXT,即下一次发送正常报文序号等于ACK序列号;总之保活报文不在窗口控制范围内 有一张图,可以很容易说明,但请仔细观察Tcp Keepalive部分:

    Tcp keepalive 如何使用

    以下环境是在Linux服务器上进行。应用程序若想使用,需要设置SO_KEEPALIVE套接口选项才能够生效

    系统内核参数配置

    tcp_keepalive_time,在TCP保活打开的情况下,最后一次数据交换到TCP发送第一个保活探测包的间隔,即允许的持续空闲时长,或者说每次正常发送心跳的周期,默认值为7200s(2h)。
    tcp_keepalive_probes 在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包次数,默认值为9(次)
    tcp_keepalive_intvl,在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包的发送频率,默认值为75s。
    发送频率tcp_keepalive_intvl乘以发送次数tcp_keepalive_probes,就得到了从开始探测到放弃探测确定连接断开的时间;
    若设置,服务器在客户端连接空闲的时候,每90秒发送一次保活探测包到客户端,若没有及时收到客户端的TCP Keepalive ACK确认,将继续等待15秒*2=30秒。总之可以在90s+30s=120秒(两分钟)时间内可检测到连接失效与否。
    以下改动,需要写入到/etc/sysctl.conf文件:
    1. net.ipv4.tcp_keepalive_time=90
    2. net.ipv4.tcp_keepalive_intvl=15
    3. net.ipv4.tcp_keepalive_probes=2
    保存退出,然后执行sysctl -p生效。可通过 sysctl -a | grep keepalive 命令检测一下是否已经生效。
    针对已经设置SO_KEEPALIVE的套接字,应用程序不用重启,内核直接生效。

    Java/netty服务器如何使用

    只需要在服务器端一方设置即可,客户端完全不用设置,比如基于netty 4服务器程序:
    1. ServerBootstrap b = new ServerBootstrap();
    2. b.group(bossGroup, workerGroup)
    3. .channel(NioServerSocketChannel.class)
    4. .option(ChannelOption.SO_BACKLOG, 100)
    5. .childOption(ChannelOption.SO_KEEPALIVE, true)
    6. .handler(new LoggingHandler(LogLevel.INFO))
    7. .childHandler(new ChannelInitializer() { @Override
    8. public void initChannel(SocketChannel ch) throws Exception {
    9. ch.pipeline().addLast( new EchoServerHandler());
    10. }
    11. }); // Start the server.
    12. ChannelFuture f = b.bind(port).sync(); // Wait until the server socket is closed.
    13. f.channel().closeFuture().sync();
    Java程序只能做到设置SO_KEEPALIVE选项,至于TCP_KEEPCNT,TCP_KEEPIDLE,TCP_KEEPINTVL等参数配置,只能依赖于sysctl配置,系统进行读取。