TCP通信中的KeepAlive - xbwen/xbwen.github.io GitHub Wiki

1、TCP协议中的KeepAlive

如果没有KeepAlive机制,只要客户端与服务器建立了TCP连接,不管两者之间是否有数据通信,不管中间的路由器是否崩溃,不管网线是否断开,连接依旧保持建立,这就会导致积累很多空闲甚至已经不可用的连接。

KeepAlive机制就是用来解决这个问题的。

KeepAlive可以在客户端实现,也可以在服务器端实现,或者两边都实现。比如,客户端每隔一段时间就利用空闲的连接向服务器发送一个数据包,这个数据包并没有其它的作用,只是为了检测一下服务器是否还处于活动状态。如果服务器未响应这个数据包,在一段时间后,客户端再次发送一个数据包,如果服务器仍然没有响应,那么客户端就将该socket关闭。

实际上,KeepAlive并不是TCP规范的一部分,RFC甚至列出了不使用它的理由。但在几乎所有的TCP/IP协议栈(不管是Linux还是Windows)中,都实现了KeepAlive功能。

在Java中,TCP socket的KeepAlive默认是关闭的,可以通过socket.setKeepAlive(true)来否启它。是的,你只能启用或者关闭,其它的参数,由操作系统决定,比如,空闲的间隔时间,在Linux上默认是2个小时。

在C语言中,则可以实现更多的、更具体的控制,如:tcp_keepalive_time(开启KeepAlive的闲置时长);tcp_keepalive_intvl(KeepAlive探测包的发送间隔); tcp_keepalive_probes(如果对方不予应答,探测包的发送次数)。

2、用Netty实现KeepAlive

尽管TCP/IP协议提供了KeepAlive机制,出于各种原因,很多开发者并不直接使用它,而是自己在应用层实现。

接下来,我们看看如何使用Netty来实现KeepAlive功能。

Netty在io.netty.handler.timeout包中提供了3个Handler类,用于处理链接的超时,分别是:IdleStateHandler,ReadTimeoutHandler,WriteTimeoutHandler。其中,IdleStateHandler是最常用的一个类。我们通过示例代码来看看如何使用它。

ChannelInitializer<SocketChannel> channelInit = new ChannelInitializer<SocketChannel>(){
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ch.pipeline().addLast(new IdleStateHandler(0, 0, 60, TimeUnit.SECONDS))
                        .addLast(new HeartbeatHandler());
        }
};

public class HeartbeatHandler extends ChannelHandlerAdapter {
    
    private static final ByteBuf PING_MSG = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("ping", CharsetUtil.UTF_8));
    
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if(evt instanceof IdleStateEvent){
            ctx.writeAndFlush(PING_MSG.duplicate()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
        }
        else{
            super.userEventTriggered(ctx, evt);
        }
    }

}

代码很简单,含义很直观:如果60秒内,当前链路没有任何数据通信(既没有收到数据,也没有发送数据),那么Netty将会触发一个IdleStateEvent事件,这时候,往对方发送一个心跳数据包(PING),如果写数据成功,表明对方还在线,如果写数据失败,则表明对方已经掉线,直接关闭该连接。