[轉載]TCP keepalive的詳解(解惑)

坚持梦想的蜗牛發表於2024-05-27

原文出自於https://www.cnblogs.com/lanyangsh/p/10926806.html

TCP是面向連線的,一般情況,兩端的應用程式可以透過傳送和接收資料得知對端的存活。
當兩端的應用程式都沒有資料傳送和接收時,如何判斷連線是否正常呢?

這就是SO_KEEPALIVE的作用。

1. SO_KEEPALIVE 的作用

1.1 SO_KEEPALIVE的定義

SO_KEEPALIVE用於開啟或者關閉保活探測,預設情況下是關閉的。

SO_KEEPALIVE開啟時,可以保持連線檢測對方主機是否崩潰,避免(伺服器)永遠阻塞於TCP連線的輸入。

相關的屬性包括:

tcp_keepalive_time、tcp_keepalive_probes、tcp_keepalive_intvl。

tcp_keepalive_intvl (integer; default: 75; since Linux 2.4)
      The number of seconds between TCP keep-alive probes.

tcp_keepalive_probes (integer; default: 9; since Linux 2.2)
      The maximum number of TCP keep-alive probes to send before
      giving up and killing the connection if no response is
      obtained from the other end.

tcp_keepalive_time (integer; default: 7200; since Linux 2.2)
      The number of seconds a connection needs to be idle before TCP
      begins sending out keep-alive probes.  Keep-alives are sent
      only when the SO_KEEPALIVE socket option is enabled.  The
      default value is 7200 seconds (2 hours).  An idle connection
      is terminated after approximately an additional 11 minutes (9
      probes an interval of 75 seconds apart) when keep-alive is
      enabled.

      Note that underlying connection tracking mechanisms and
      application timeouts may be much shorter.

這些屬性可以在/proc/sys/net/ipv4/下檢視:

cat /proc/sys/net/ipv4/tcp_keepalive_time
7200

cat /proc/sys/net/ipv4/tcp_keepalive_probes
9

cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75

也可以透過命令列檢視:

sudo sysctl -a | grep keepalive

net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_intvl = 75

1.2 連線探活的過程

開啟SO_KEEPALIVE後,如果2小時內在此套介面的任一方向都沒有資料交換,TCP就自動給對方發一個保持存活探測分節(keepalive probe)。這是一個對方必須響應的TCP分節.

它會導致以下三種情況:

對方接收一切正常:以期望的ACK響應。2小時後,TCP將發出另一個探測分節。
對方已崩潰且已重新啟動:以RST響應。套介面的待處理錯誤被置為ECONNRESET,套接 口本身則被關閉。
對方無任何響應:源自berkeley的TCP傳送另外8個探測分節,相隔75秒一個,試圖得到一個響應。一共嘗試9次,即在發出第一個探測分節11分鐘 15秒後若仍無響應就放棄。套介面的待處理錯誤被置為ETIMEOUT,套介面本身則被關閉。如ICMP錯誤是“host unreachable(主機不可達)”,說明對方主機並沒有崩潰,但是不可達,這種情況下待處理錯誤被置為 EHOSTUNREACH。
根據上面的介紹我們可以知道對端以一種非優雅的方式斷開連線的時候,我們可以設定SO_KEEPALIVE屬性使得我們在2小時以後發現對方的TCP連線是否依然存在。

int keepAlive = 1;

setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive));

如果我們不能接受如此之長的等待時間,怎麼辦?

2.設定TCP KEEPALIVE

上面提到,SO_KEEPALIVE預設的時間間隔太長,不利於應用程式檢測連線狀態。

解決方法有2種:

全域性設定

針對單個連線設定

2.1 全域性設定

在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,程式不用重啟,核心直接生效.

這種方法設定的全域性的引數,針對整個系統生效,對單個socket的設定不夠友好。

2.2 針對單個連線設定

我們可以使用TCP的TCP_KEEPCNT、TCP_KEEPIDLE、TCP_KEEPINTVL3個選項。
這些選項是連線級別的,每個socket都可以設定這些屬性。

這些選項的定義,可以透過man檢視。


man 7 tcp
socket option:

TCP_KEEPCNT (since Linux 2.4)
      The maximum number of keepalive probes TCP should send before
      dropping the connection.  This option should not be used in
      code intended to be portable.
      關閉一個非活躍連線之前的最大重試次數。
      該選項不具備可移植性。

TCP_KEEPIDLE (since Linux 2.4)
      The time (in seconds) the connection needs to remain idle
      before TCP starts sending keepalive probes, if the socket
      option SO_KEEPALIVE has been set on this socket.  This option
      should not be used in code intended to be portable.
      設定連線上如果沒有資料傳送的話,多久後傳送keepalive探測分組,單位是秒
      該選項不具備可移植性。

TCP_KEEPINTVL (since Linux 2.4)
      The time (in seconds) between individual keepalive probes.
      This option should not be used in code intended to be
      portable.
      前後兩次探測之間的時間間隔,單位是秒
      該選項不具備可移植性。

程式碼層面的設定步驟:


int keepAlive = 1;    // 非0值,開啟keepalive屬性

int keepIdle = 60;    // 如該連線在60秒內沒有任何資料往來,則進行此TCP層的探測

int keepInterval = 5; // 探測發包間隔為5秒

int keepCount = 3;        // 嘗試探測的最多次數

// 開啟探活
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));

setsockopt(sockfd, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));

setsockopt(sockfd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));

setsockopt(sockfd, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount) 

3.為什麼應用層需要heart beat/心跳包?

透過上面的介紹,感覺TCP keepalive已經很牛逼了,但為什麼還會提到應用層的心跳呢?

目前瞭解的原因包括兩個:

TCP keepalive處於傳輸層,由作業系統負責,能夠判斷程序存在,網路通暢,但無法判斷程序阻塞或死鎖等問題。
客戶端與伺服器之間有四層代理或負載均衡,即在傳輸層之上的代理,只有傳輸層以上的資料才被轉發,例如socks5等
所以,基於以上原因,有時候還是需要應用程式自己去設計心跳規則的。
可以服務端負責週期傳送心跳包,檢測客戶端,也可以客戶端負責傳送心跳包,或者服服務端和客戶端同時傳送心跳包。

可以根據具體的應用場景進行設計。

相關文章