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_KEEPINTVL
3個選項。
這些選項是連接級別的,每個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等
所以,基於以上原因,有時候還是需要應用程序自己去設計心跳規則的。
可以服務端負責周期發送心跳包,檢測客戶端,也可以客戶端負責發送心跳包,或者服服務端和客戶端同時發送心跳包。
可以根據具體的應用場景進行設計。
參考
《UNIX網絡編程卷1》
《Linux多線程服務端編程》
setsockopt, SO_KEEPALIVE and Heartbeats
為什么基於TCP的應用需要心跳包(TCP keep-alive原理分析