歡迎光臨
每天分享高質量文章

聊聊 TCP 中的 KeepAlive 機制

(點擊上方公眾號,可快速關註)


來源:王爵,

biezhi.me/article/talk-tcp-keepalive

服務端的系統設置中經常會和底層協議打交道,我們有必要重溫一下曾經那些“聽過”卻不熟悉的名詞。 今天聊的話題是 KeepAlive,在實際應用中又是怎麼使用的?

為什麼有Keepalive?

大家都做過電梯吧,假設電梯來了你先進去,你朋友還沒進來,過一段時間電梯門就會自動關閉, 你應該沒遇到過哪個電梯會一直等你朋友來了才關門的。如果真是那樣,那別的樓層的小姐姐們會炸了~

我們舉個編程中的例子來解釋下,我編寫了一個服務端程式S和一個客戶端程式C,客戶端向服務端發送 一個訊息:

服務端收到訊息後一看,瞧給你牛*的,然後沒理客戶端,傻狗客戶端一直在等待,但是不知道是不是服務器掛掉了? 這時候TCP協議提出一個辦法,當客戶端端等待超過一定時間後自動給服務端發送一個空的報文, 如果對方回覆了這個報文證明連接還存活著,如果對方沒有報文傳回且進行了多次嘗試都是一樣, 那麼就認為連接已經丟失,客戶端就沒必要繼續保持連接了。 如果沒有這種機制就會有很多空閑的連接占用著系統資源。

KeepAlive並不是TCP協議規範的一部分,但在幾乎所有的TCP/IP協議棧(不管是Linux還是Windows)中,都實現了KeepAlive功能

RFC1122#TCP Keep-Alives

https://tools.ietf.org/html/rfc1122#page-101

如何設置它?

在設置之前我們先來看看KeepAlive都支持哪些設置項

  1. KeepAlive預設情況下是關閉的,可以被上層應用開啟和關閉

  2. tcp_keepalive_time: KeepAlive的空閑時長,或者說每次正常發送心跳的周期,預設值為7200s(2小時)

  3. tcp_keepalive_intvl: KeepAlive探測包的發送間隔,預設值為75s

  4. tcp_keepalive_probes: 在tcp_keepalive_time之後,沒有接收到對方確認,繼續發送保活探測包次數,預設值為9(次)

我們講講在Linux操作系統和使用Java、C語言和Nginx中如何設置

在Linux內核設置

KeepAlive預設不是開啟的,如果想使用KeepAlive,需要在你的應用中設置SO_KEEPALIVE才可以生效。

查看當前的配置:

cat /proc/sys/net/ipv4/tcp_keepalive_time

cat /proc/sys/net/ipv4/tcp_keepalive_intvl

cat /proc/sys/net/ipv4/tcp_keepalive_probes

在Linux中我們可以通過修改 /etc/sysctl.conf 的全域性配置:

net.ipv4.tcp_keepalive_time=7200

net.ipv4.tcp_keepalive_intvl=75

net.ipv4.tcp_keepalive_probes=9

添加上面的配置後輸入 sysctl -p 使其生效,你可以使用 sysctl -a | grep keepalive 命令來查看當前的預設配置

如果應用中已經設置SO_KEEPALIVE,程式不用重啟,內核直接生效

使用Netty4設置

這裡我們使用常用的Java網絡框架Netty來設置,只需要在服務端設置即可:

EventLoopGroup bossGroup   = new NioEventLoopGroup(1);

EventLoopGroup workerGroup = new NioEventLoopGroup();

try {

    ServerBootstrap b = new ServerBootstrap();

    b.group(bossGroup, workerGroup)

            .channel(NioServerSocketChannel.class)

            .option(ChannelOption.SO_BACKLOG, 100)

            .childOption(ChannelOption.SO_KEEPALIVE, true)

            .handler(new LoggingHandler(LogLevel.INFO));

 

    // Start the server.

    ChannelFuture f = b.bind(8088).sync();

    // Wait until the server socket is closed.

    f.channel().closeFuture().sync();

} finally {

    // Shut down all event loops to terminate all threads.

    bossGroup.shutdownGracefully();

    workerGroup.shutdownGracefully();

}

這段代碼來自經典的echo服務器,我們在childOption中開啟了SO_KEEPALIVE。 Java程式只能做到設置SO_KEEPALIVE選項,其他配置項只能依賴於sysctl配置,系統進行讀取。

C語言設置

函式原型:

#include

 

int setsockopt(int socket, int level, int option_name,

      const void *option_value, socklen_t option_len);

我們在需要使能Keepalive的socket上面呼叫setsockopt函式便可以打開該socket上面的keepalive。

  1. 第一個引數是要設置的套接字

  2. 第二個引數是SOL_SOCKET

  3. 第三個引數必須是SO_KEEPALIVE

  4. 第四個引數必須是一個布爾整型值,0表示關閉,1表示打開

  5. 最後一個引數是第四個引數值的大小。

呼叫例子:

int socket(int domain, int type, int protocol)

{

  int (*libc_socket)(int, int, int);

  int s, optval;

  char *env;

 

  *(void **)(&libc;_socket) = dlsym(RTLD_NEXT, “socket”);

  if(dlerror()) {

    errno = EACCES;

    return -1;

  }

 

  if((s = (*libc_socket)(domain, type, protocol)) != -1) {

    if((domain == PF_INET) && (type == SOCK_STREAM)) {

      if(!(env = getenv(“KEEPALIVE”)) || strcasecmp(env, “off”)) {

        optval = 1;

      } else {

        optval = 0;

      }

      if(!(env = getenv(“KEEPALIVE”)) || strcasecmp(env, “skip”)) {

        setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &optval;, sizeof(optval));

      }

#ifdef TCP_KEEPCNT

      if((env = getenv(“KEEPCNT”)) && ((optval = atoi(env)) >= 0)) {

        setsockopt(s, SOL_TCP, TCP_KEEPCNT, &optval;, sizeof(optval));

      }

#endif

#ifdef TCP_KEEPIDLE

      if((env = getenv(“KEEPIDLE”)) && ((optval = atoi(env)) >= 0)) {

        setsockopt(s, SOL_TCP, TCP_KEEPIDLE, &optval;, sizeof(optval));

      }

#endif

#ifdef TCP_KEEPINTVL

      if((env = getenv(“KEEPINTVL”)) && ((optval = atoi(env)) >= 0)) {

        setsockopt(s, SOL_TCP, TCP_KEEPINTVL, &optval;, sizeof(optval));

      }

#endif

    }

  }

 

   return s;

}

代碼摘取自libkeepalive原始碼,C語言可以設置更為詳細的TCP內核引數

在Nginx中配置

在Nginx中配置TCP的KeepAlive非常簡單,在listen指令下配置so_keepalive就可以了,具體配置

so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]

this parameter (1.1.11) configures the “TCP keepalive” behavior for the listening socket. If this parameter is omitted then the operating system’s settings will be in effect for the socket. If it is set to the value “on”, the SO_KEEPALIVE option is turned on for the socket. If it is set to the value “off”, the SO_KEEPALIVE option is turned off for the socket. Some operating systems support setting of TCP keepalive parameters on a per-socket basis using the TCP_KEEPIDLE, TCP_KEEPINTVL, and TCP_KEEPCNT socket options. On such systems (currently, Linux 2.4+, NetBSD 5+, and FreeBSD 9.0-STABLE), they can be configured using the keepidle, keepintvl, and keepcnt parameters. One or two parameters may be omitted, in which case the system default setting for the corresponding socket option will be in effect.

例子

so_keepalive=30m::10

will set the idle timeout (TCP_KEEPIDLE) to 30 minutes,

leave the probe interval (TCP_KEEPINTVL) at its system default,

and set the probes count (TCP_KEEPCNT) to 10 probes.

使用的場景

一般我們使用KeepAlive時會修改空閑時長,避免資源浪費,系統內核會為每一個TCP連接 建立一個保護記錄,相對於應用層面效率更高。

常見的幾種使用場景:

  1. 檢測掛掉的連接(導致連接掛掉的原因很多,如服務停止、網絡波動、宕機、應用重啟等)

  2. 防止因為網絡不活動而斷連(使用NAT代理或者防火牆的時候,經常會出現這種問題)

  3. TCP層面的心跳檢測

KeepAlive通過定時發送探測包來探測連接的對端是否存活, 但通常也會許多在業務層面處理的,他們之間的特點:

  1. TCP自帶的KeepAlive使用簡單,發送的資料包相比應用層心跳檢測包更小,僅提供檢測連接功能

  2. 應用層心跳包不依賴於傳輸層協議,無論傳輸層協議是TCP還是UDP都可以用

  3. 應用層心跳包可以定製,可以應對更複雜的情況或傳輸一些額外信息

  4. KeepAlive僅代表連接保持著,而心跳包往往還代表客戶端可正常工作

和Http中Keep-Alive的關係

  1. HTTP協議的Keep-Alive意圖在於連接復用,同一個連接上串行方式傳遞請求-響應資料

  2. TCP的KeepAlive機制意圖在於保活、心跳,檢測連接錯誤

參考資料

  • Keepalive

    https://en.wikipedia.org/wiki/Keepalive

  • TCP Keepalive HOWTO

    http://www.tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/

  • 隨手記之TCP Keepalive筆記

    http://www.blogjava.net/yongboy/archive/2015/04/14/424413.html

  • 理解TCP之Keepalive

    http://www.firefoxbug.com/index.php/archives/2805/

看完本文有收穫?請轉發分享給更多人

關註「ImportNew」,看技術乾貨

赞(0)

分享創造快樂