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

聊聊 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)

分享創造快樂