1. 程式人生 > >TCP的定時器系列 — 保活定時器 Keepalive

TCP的定時器系列 — 保活定時器 Keepalive

主要內容:保活定時器的實現,TCP_USER_TIMEOUT選項的實現。

核心版本:3.15.2

原理

HTTP有Keepalive功能,TCP也有Keepalive功能,雖然都叫Keepalive,但是它們的目的卻是不一樣的。

為了說明這一點,先來看下長連線和短連線的定義。

連線的“長短”是什麼?

短連線:建立一條連線,傳輸一個請求,馬上關閉連線。

長連線:建立一條連線,傳輸一個請求,過會兒,又傳輸若干個請求,最後再關閉連線。

長連線的好處是顯而易見的,多個請求可以複用一條連線,省去連線建立和釋放的時間開銷和系統呼叫,

但也意味著伺服器的一部分資源會被長時間佔用著。

HTTP的Keepalive,顧名思義,目的在於延長連線的時間,以便在同一條連線中傳輸多個HTTP請求。

HTTP伺服器一般會提供Keepalive Timeout引數,用來決定連線保持多久,什麼時候關閉連線。

當連線使用了Keepalive功能時,對於客戶端傳送過來的一個請求,伺服器端會發送一個響應,然後開始計時,

如果經過Timeout時間後,客戶端沒有再發送請求過來,伺服器端就把連線關了,不再保持連線了。

TCP的Keepalive,是掛羊頭賣狗肉的,目的在於看看對方有沒有發生異常,如果有異常就及時關閉連線。

當傳輸雙方不主動關閉連線時,就算雙方沒有交換任何資料,連線也是一直有效的。

如果這個時候對端、中間網路出現異常而導致連線不可用,本端如何得知這一資訊呢?

答案就是保活定時器。它每隔一段時間會超時,超時後會檢查連線是否空閒太久了,如果空閒的時間超過

了設定時間,就會發送探測報文。然後通過對端是否響應、響應是否符合預期,來判斷對端是否正常,

如果不正常,就主動關閉連線,而不用等待HTTP層的關閉了。

當伺服器傳送探測報文時,客戶端可能處於4種不同的情況:仍然正常執行、已經崩潰、已經崩潰並重啟了、

由於中間鏈路問題不可達。在不同的情況下,伺服器會得到不一樣的反饋。

(1) 客戶主機依然正常執行,並且從伺服器端可達

客戶端的TCP響應正常,從而伺服器端知道對方是正常的。保活定時器會在兩小時以後繼續觸發。

(2) 客戶主機已經崩潰,並且關閉或者正在重新啟動

客戶端的TCP沒有響應,伺服器沒有收到對探測包的響應,此後每隔75s傳送探測報文,一共傳送9次。

socket函式會返回-1,errno設定為ETIMEDOUT,表示連線超時。

(3) 客戶主機已經崩潰,並且重新啟動了

客戶端的TCP傳送RST,伺服器端收到後關閉此連線。

socket函式會返回-1,errno設定為ECONNRESET,表示連線被對端復位了。

(4) 客戶主機依然正常執行,但是從伺服器不可達

雙方的反應和第二種是一樣的,因為伺服器不能區分對端異常與中間鏈路異常。

socket函式會返回-1,errno設定為EHOSTUNREACH,表示對端不可達。

選項

核心預設並不使用TCP Keepalive功能,除非使用者設定了SO_KEEPALIVE選項。

有兩種方式可以自行調整保活定時器的引數:一種是修改TCP引數,一種是使用TCP層選項。

(1) TCP引數

tcp_keepalive_time

最後一次資料交換到TCP傳送第一個保活探測報文的時間,即允許連線空閒的時間,預設為7200s。

tcp_keepalive_intvl

保活探測報文的重傳時間,預設為75s。

tcp_keepalive_probes

保活探測報文的傳送次數,預設為9次。

Q:一次完整的保活探測需要花費多長時間?

A:tcp_keepalive_time + tcp_keepalive_intvl * tcp_keepalive_probes,預設值為7875s。

如果覺得兩個多小時太長了,可以自行調整上述引數。

(2) TCP層選項

TCP_KEEPIDLE:含義同tcp_keepalive_time。

TCP_KEEPINTVL:含義同tcp_keepalive_intvl。

TCP_KEEPCNT:含義同tcp_keepalive_probes。

Q:既然有了TCP引數可供調整,為什麼還增加了上述的TCP層選項?

A:TCP引數是面向本機的所有TCP連線,一旦調整了,對所有的連線都有效。

而TCP層選項是面向一條連線的,一旦調整了,只對本條連線有效。

啟用

在連線建立後,可以通過設定SO_KEEPALIVE選項,來啟用保活定時器。

int keepalive = 1;

setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive));

  1. int sock_setsockopt(struct socket *sock, int level, int optname, char __user *optval,   
  2.     unsigned int optlen)  
  3. {  
  4.     ...  
  5.     case SO_KEEPALIVE:  
  6. #ifdef CONFIG_INET  
  7.         if (sk->sk_protocol == IPPROTO_TCP && sk->sk_type == SOCK_STREAM)  
  8.             tcp_set_keepalive(sk, valbool); /* 啟用或刪除保活定時器 */
  9. #endif  
  10.         sock_valbool_flag(sk, SOCK_KEEPOPEN, valbool); /* 設定或取消SOCK_KEEPOPEN標誌位 */
  11.         break;  
  12.     ...  
  13. }  
  14. static inline void sock_valbool_flag (struct sock *sk, int bit, int valbool)  
  15. {  
  16.     if (valbool)  
  17.         sock_set_flag(sk, bit);  
  18.     else
  19.         sock_reset_flag(sk, bit);  
  20. }  
  1. void tcp_set_keepalive(struct sock *sk, int val)  
  2. {  
  3.     /* 不在以下兩個狀態設定保活定時器: 
  4.      * TCP_CLOSE:sk_timer用作FIN_WAIT2定時器 
  5.      * TCP_LISTEN:sk_timer用作SYNACK重傳定時器 
  6.      */
  7.     if ((1 << sk->sk_state) & (TCPF_CLOSE | TCPF_LISTEN))  
  8.         return;  
  9.     /* 如果SO_KEEPALIVE選項值為1,且此前沒有設定SOCK_KEEPOPEN標誌, 
  10.      * 則啟用sk_timer,用作保活定時器。 
  11.      */
  12.     if (val && !sock_flag(sk, SOCK_KEEPOPEN))  
  13.         inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tcp_sk(sk)));  
  14.     elseif (!val)  
  15.         /* 如果SO_KEEPALIVE選項值為0,則刪除保活定時器 */
  16.         inet_csk_delete_keepalive_timer(sk);  
  17. }  
  18. /* 保活定時器的超時時間 */
  19. static inline int keepalive_time_when(const struct tcp_sock *tp)  
  20. {  
  21.     return tp->keepalive_time ? : sysctl_tcp_keepalive_time;  
  22. }  
  23. void inet_csk_reset_keepalive_timer (struc sock *sk, unsigned long len)  
  24. {  
  25.     sk_reset_timer(sk, &sk->sk_timer, jiffies + len);  
  26. }  

可以使用TCP層選項來動態調整保活定時器的引數。

int keepidle = 600;

int keepintvl = 10;

int keepcnt = 6;

setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle));

setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl));

setsockopt(fd, SOL_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt));

  1. struct tcp_sock {  
  2.     ...  
  3.     /* 最後一次接收到ACK的時間 */
  4.     u32 rcv_tstamp; /* timestamp of last received ACK (for keepalives) */
  5.     ...  
  6.     /* time before keep alive takes place, 空閒多久後才傳送探測報文 */
  7.     unsigned int keepalive_time;  
  8.     /* time iterval between keep alive probes */
  9.     unsigned int keepalive_intvl; /* 探測報文之間的時間間隔 */
  10.     /* num of allowed keep alive probes */
  11.     u8 keepalive_probes; /* 探測報文的傳送次數 */
  12.     ...  
  13.     struct {  
  14.         ...  
  15.         /* 最後一次接收到帶負荷的報文的時間 */
  16.         __u32 lrcvtime; /* timestamp of last received data packet */
  17.         ...  
  18.     } icsk_ack;  
  19.     ...  
  20. };  
  21. #define TCP_KEEPIDLE 4/* Start Keepalives after this period */
  22. #define TCP_KEEPINTVL 5/* Interval between keepalives */
  23. #define TCP_KEEPCNT 6/* Number of keepalives before death */
  24. #define MAX_TCP_KEEPIDLE 32767
  25. #define MAX_TCP_KEEPINTVL 32767
  26. #define MAX_TCP_KEEPCNT 127
  1. staticint do_tcp_setsockopt(struct sock *sk, int level, int optname, char __user *optval,  
  2.     unsigned int optlen)  
  3. {  
  4.     ...  
  5.     case TCP_KEEPIDLE:  
  6.        if (val < 1 || val > MAX_TCP_KEEPIDLE)  
  7.            err = -EINVAL;  
  8.         else {  
  9.             tp->keepalive_time = val * HZ; /* 設定新的空閒時間 */
  10.             /* 如果有使用SO_KEEPALIVE選項,連線處於非監聽非結束的狀態。 
  11.              * 這個時候保活定時器已經在計時了,這裡設定新的超時時間。 
  12.              */
  13.             if (sock_flag(sk, SOCK_KEEPOPEN) &&   
  14.                 !((1 << sk->sk_state) & (TCPF_CLOSE | TCPF_LISTEN))) {  
  15.                 u32 elapsed = keepalive_time_elapsed(tp); /* 連線已經經歷的空閒時間 */
  16.                 if (tp->keepalive_time > elapsed)  
  17.                     elapsed = tp->keepalive_time - elapsed; /* 接著等待的時間,然後超時 */
  18. 相關推薦

    TCP定時系列定時 Keepalive

    主要內容:保活定時器的實現,TCP_USER_TIMEOUT選項的實現。 核心版本:3.15.2 原理 HTTP有Keepalive功能,TCP也有Keepalive功能,雖然都叫Keepalive,但是它們的目的卻是不一樣的。 為了說

    TCP定時系列定時

    主要內容:保活定時器的實現,TCP_USER_TIMEOUT選項的實現。 核心版本:3.15.2 原理 HTTP有Keepalive功能,TCP也有Keepalive功能,雖然都叫Keepalive,但是它們的目的卻是不一樣的。 為了說明這一點,先來看下長連線和短連線

    TCP/IP詳解--幾類定時的作用(重傳 定時

    與資料鏈路層的ARQ協議相類似,TCP使用超時重發的重傳機制。即:TCP每傳送一個報文段,就對此報文段設定一個超時重傳計時器。此計時器設定的超時重傳時間RTO(Retransmission Time-Out)應當略大於TCP報文段的平均往返時延RTT,一般可取RTO=2R

    TCP堅持定時TCP定時

    TCP一共有四個主要的定時器,前面已經講到了超時定時器,2MSL定時器,(MSL是指任何報文段被丟棄前在網路內的最長存活時間)另外的兩個是: 堅持定時器 1,     堅持定時器的意義; 在TCP連線雙方,均有一個接收快取,當接收快取滿時,接收端會回覆傳送端一個視窗大小為

    TCP/IP學習筆記(13)-TCP堅持定時TCP定時

    TCP一共有四個主要的定時器,前面已經講到了一個--超時定時器--是TCP裡面最複雜的一個,另外的三個是:         1.    堅持定時器         2.    保活定時器         3.    2MSL定時器 其中堅持定時器用於防止通告視窗為0以後雙方

    TCP定時系列 — 零視窗探測定時

    主要內容:零視窗探測定時器的實現。 核心版本:3.15.2 出現以下情況時,TCP接收方的接收緩衝區將被塞滿資料: 傳送方的傳送速度大於接收方的接收速度。 接收方的應用程式未能及時從接收緩衝區中讀取資料。 當接收方的接收緩衝區滿了以後,會把響應報文中的通告視窗欄位置為

    C# 定時機制引起的記憶體洩露問題

    C# 中有三種定時器,System.Windows.Forms 中的定時器和 System.Timers.Timer 的工作方式是完全一樣的,所以,這裡我們僅討論 System.Timers.Timer 和 System.Threading.Timer 1、定時器保活 先來看一個例子: class Progr

    補習系列(9)-springboot 定時,你用對了嗎

    empty cront apps 任務並發 轉發 gis execute 大小 定義 目錄 簡介 一、應用啟動任務 二、JDK 自帶調度線程池 三、@Scheduled 定制 @Scheduled 線程池 四、@Async 定制 @Async 線程池 小結 簡介 大

    深入理解定時系列第一篇——理解setTimeout和setInterval

    前面的話   很長時間以來,定時器一直是javascript動畫的核心技術。但是,關於定時器,人們通常只瞭解如何使用setTimeout()和setInterval(),對它們的內在執行機制並不理解,對於與預想不同的實際執行狀況也無法解決。本文將詳細介紹定時器的相關內容 setTimeout()  

    深入理解定時系列第三篇——定時應用(時鐘、倒計時、秒錶和鬧鐘)

    前面的話   本文屬於定時器的應用部分,分別用於實現與時間相關的四個應用,包括時鐘、倒計時、秒錶和鬧鐘。與時間相關需要用到時間和日期物件Date,詳細情況移步至此 時鐘   最簡單的時鐘製作辦法是通過正則表示式的exec()方法,將時間物件的字串中的時間部分截取出來,使用定時器重新整理即可 &

    深入理解定時系列第二篇——被譽為神器的requestAnimationFrame

    前面的話   與setTimeout和setInterval不同,requestAnimationFrame不需要設定時間間隔。這有什麼好處呢?為什麼requestAnimationFrame被稱為神器呢?本文將詳細介紹HTML5新增的定時器requestAnimationFrame 引入   計時

    定時系列-被譽為神器的requestAnimationFrame

       大多數電腦顯示器的重新整理頻率是60Hz,大概相當於每秒鐘重繪60次。大多數瀏覽器都會對重繪操作加以限制,不超過顯示器的重繪頻率,因為即使超過那個頻率使用者體驗也不會有提升。因此,最平滑動畫的最佳迴圈間隔是1000ms/60,約等於16.6ms

    Python系列之迴圈定時

    近期在學習並使用Python開發一些小工具,在這裡記錄方便回憶,也與各位開始走上這條路的朋友共勉,如有不正確希望指正,謝謝! 開始使用定時器時,度娘了下有沒好的例子,本人比較懶,希望能直接使用。確實找到了一些,但是大多隻是很直白的程式碼,自己打算整理一下。 我選用了thr

    定時/計數器0之定時

    .com 函數調用 wid 延時 mod main images .cn cnblogs /* 效果說明: 定時器中斷:通過單片機計數使程序執行 一秒中斷一次,中斷發生時高四位亮一秒,中斷發生後又回到主程序 */ #include <

    C# System.Timers.Timer定時的使用和定時自動清理內存應用

    for process work proc program 指定時間 handle 清理 interval 項目比較大有時候會比較卡,雖然有GC自動清理機制,但是還是有不盡人意的地方。所以嘗試在項目啟動文件中,手動寫了一個定時器,定時清理內存,加快項目運行速度。 pub

    Windows服務上使用bat定時執行php

    保存 掛載 color website window 服務 註意 一個 con windows上和linux上有一個類似的cmd和bat文件,bat文件類似於shell文件,執行這個bat文件,就相當於依次執行裏面的命令(當然,還可以通過邏輯來實現編程),所以,我們可以

    runloop 和 CFRunLoop - 定時 - NSTimer 和 GCD定時

    決定 etc ont tro mode .com int schedule lin 1. 2、 1 #import "ViewController.h" 2 3 @interface ViewController () 4 @property (no

    STM32同時開啟兩個定時,其中一個定時不能設定斷點的原因

    最近在編寫程式的時候發現stm32微控制器的定時器不同的型別其配置是不一樣的。 在程式設計的過程中開了兩個定時器,結果在除錯程式的時候發現TIM6中斷程式不能設定斷點,就說明這段程式可能沒有被執行,後來我又換了TIM1也是一樣不行。 檢視資料手冊如下:  從stm3

    MySQL 事件排程(Event Scheduler)建立定時任務

    事件排程器(Event Scheduler)是在MySQLv5.1.6中新增的一個功能,它相當於一個定時器,可以在指定的時間點執行一條SQL語句或一個語句塊,也可以用於在固定間隔重複執行。事件排程器相當於作業系統中的定時任務(如:Linux中的cron、Window中的計劃任務),但MySql的事

    使用Spring的定時@Scheduled註解實現定時任務

    在很多時候我們在專案中需要週期性地執行一些操作,並且這些操作不能通過簡單的for迴圈和while迴圈來實現,因此我們需要有一個可以實現定時操作的方法,在spring中就有這麼一個非常方便的方法,下面就簡單記錄下@Scheduled註解的使用方法。 1、首先我們需要一個spring專案,這個是前