Linux內核源碼剖析之TCP保活機制(KeepAlive)

寫在前面:

版本信息:

Linux內核2.6.24(大部分centos、ubuntu應該都在3.1+。但是2.6的版本比較穩定,后續版本本質變化也不是很大)

ipv4 協議

https://blog.csdn.net/ComplexMaze/article/details/124201088

本文使用案例如上地址,感謝案例的分享,本篇文章核心部分還是在Linux內核源碼分析~

為什么寫下這篇文章,因為在實際項目中,是無法避免TCP通訊(對于這點,可能大部分Java程序員感受不到底層的網絡通訊),正因為無法避免TCP通訊,恰好TCP通訊存在三次握手和四次揮手的過程,如果建立一次連接就三次握手和四次揮手,而我們清楚的知道三次握手和四次揮手是同步的過程,此過程也會帶來不少的時間浪費和資源的浪費。所以Linux內核TCP網絡協議棧就出現了KeepAlive機制,此機制減少三次握手和四次揮手次數,第一次建立連接后保持長連接,后續通訊就可以只考慮發送數據報文即可。往往出現一個機制解決某個問題,其他問題又出現,如果所有連接都建立長連接保活機制,而連接數又有限制,此時該如何解決呢?如下代碼,Linux使用心跳機制去檢測連接是否存活~

#define TCP_KEEPALIVE_TIME	(120*60*HZ)	    // 首次,2小時
#define TCP_KEEPALIVE_PROBES	9		    // 重試9次
#define TCP_KEEPALIVE_INTVL	(75*HZ)         // 后續,每75秒一次
  1. 在Linux內核中默認關閉KeepAlive
  2. 開啟KeepAlive后,默認2小時后往對端發送心跳包,檢查是否還活著
  3. 默認后續每75秒往對端發送心跳包,檢查是否還活著
  4. 默認當對端9次都沒有響應報文就發送RST報文,斷開TCP連接,釋放資源!
  5. 當然這一切參數都可以配置,通過sys_setsockopt系統調用,當然setsockopt函數庫就行啦

回到上述描述的話題,往往出現一個機制解決某個問題,其他問題又出現。解決了頻繁握手和揮手的時間,但是連接數量不夠的問題又出現了,可能很多連接建立在那里,完全不通訊了,或者對端已經斷網,或者宕機等等原因占用連接不釋放,而Linux默認一個連接存活檢測需要2個小時+ 才去檢測對端是否活著,如果說服務器的負荷比較大,2小時才檢測一次,會導致正常請求無法進行,所以此參數需要通過setsockopt函數庫重新設置參數(當然,如果是Java等等虛擬機語言,本身也有自身的封裝函數去操作setsockopt函數庫,或者直接調用sys_setsockopt系統調用,這個需要看語言手冊~!)話又說回來,如果設置的閾值大小、時間太短的問題也會很明顯,一直都在發心跳包檢測,甚至性能損耗大于了握手和揮手的時間,所以需要根據業務環境、服務器的硬件從性能損耗和空閑連接數量做折中考慮~

案例:

下面是C語言的服務端的案例源碼,此案例是借用的,但是我們重點關心機制~

/*server.c*/
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include <netinet/tcp.h>
?
#define PORT 4000//端口號 
#define BACKLOG 5/*最大監聽數*/ 
#define MAX_DATA 100//接收到的數據最大程度 
?
int main(){int sockfd,new_fd;/*socket句柄和建立連接后的句柄*/struct sockaddr_in my_addr;/*本方地址信息結構體,下面有具體的屬性賦值*/struct sockaddr_in their_addr;/*對方地址信息*/int sin_size;char buf[MAX_DATA];//儲存接收數據 
?sockfd=socket(AF_INET,SOCK_STREAM,0);//建立socket if(sockfd==-1){printf("socket failed:%d",errno);return -1;}my_addr.sin_family=AF_INET;/*該屬性表示接收本機或其他機器傳輸*/my_addr.sin_port=htons(PORT);/*端口號*/my_addr.sin_addr.s_addr=htonl(INADDR_ANY);/*IP,括號內容表示本機IP*/bzero(&(my_addr.sin_zero),8);/*將其他屬性置0*/if(bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr))<0){//綁定地址結構體和socketprintf("bind error");return -1;}listen(sockfd,BACKLOG);//開啟監聽 ,第二個參數是最大監聽數 while(1){sin_size=sizeof(struct sockaddr_in);new_fd=accept(sockfd,(struct sockaddr*)&their_addr,&sin_size);//在這里阻塞知道接收到消息,參數分別是socket句柄,接收到的地址信息以及大小 // 開啟保活,1分鐘內探測不到,斷開連接int keep_alive = 1;int keep_idle = 3;int keep_interval = 1;int keep_count = 57;if (setsockopt(new_fd, SOL_SOCKET, SO_KEEPALIVE, &keep_alive, sizeof(keep_alive))) {perror("Error setsockopt(SO_KEEPALIVE) failed");exit(1);}if (setsockopt(new_fd, IPPROTO_TCP, TCP_KEEPIDLE, &keep_idle, sizeof(keep_idle))) {perror("Error setsockopt(TCP_KEEPIDLE) failed");exit(1);}if (setsockopt(new_fd, SOL_TCP, TCP_KEEPINTVL, (void *)&keep_interval, sizeof(keep_interval))) {perror("Error setsockopt(TCP_KEEPINTVL) failed");exit(1);}if (setsockopt(new_fd, SOL_TCP, TCP_KEEPCNT, (void *)&keep_count, sizeof(keep_count))) {perror("Error setsockopt(TCP_KEEPCNT) failed");exit(1);}while(new_fd != -1) {recv(new_fd,buf,MAX_DATA,0);//將接收數據打入buf,參數分別是句柄,儲存處,最大長度,其他信息(設為0即可)。 printf("%s",buf);}}return 0;
} 

此服務端案例非常的簡單,當客戶端與服務端建立連接后,修改KeepAlive的機制參數,使用setsockopt庫函數修改。

SO_KEEPALIVE:開啟KeepAlive機制

TCP_KEEPIDLE:首次檢測的時長

TCP_KEEPINTVL:下次檢測的間隔時長

TCP_KEEPCNT:重試閾值次數

源碼分析:

首先看到TCP_KEEPIDLE、TCP_KEEPINTVL、TCP_KEEPCNT這三個參數的設置,源碼在net/ipv4/tcp.c 文件do_tcp_setsockopt方法,此方法由sys_setsockopt系統調用方法調用。

static int do_tcp_setsockopt(struct sock *sk, int level,int optname, char __user *optval, int optlen)
{struct tcp_sock *tp = tcp_sk(sk);struct inet_connection_sock *icsk = inet_csk(sk);int val;int err = 0;switch (optname) {…………case TCP_KEEPIDLE:		// 設置第一次觸發的時間if (val < 1 || val > MAX_TCP_KEEPIDLE)err = -EINVAL;else {// 算出設置的時間tp->keepalive_time = val * HZ;// 如果KeepAlive機制已開啟,并且當前不是關閉狀態和監聽狀態。if (sock_flag(sk, SOCK_KEEPOPEN) &&!((1 << sk->sk_state) &(TCPF_CLOSE | TCPF_LISTEN))) {// 當前時間 - 上次ACK的時候 = 相對時間__u32 elapsed = tcp_time_stamp - tp->rcv_tstamp;if (tp->keepalive_time > elapsed)// 如果上次ACK同步的時間小于設置的時間,那就把剩余的時間算出來elapsed = tp->keepalive_time - elapsed;else// 如果上次ACK同步的時間大于設置的時間,那就立馬檢測elapsed = 0;// 設置內核的定時器inet_csk_reset_keepalive_timer(sk, elapsed);}}break;case TCP_KEEPINTVL:			// 設置每次的間隔時間if (val < 1 || val > MAX_TCP_KEEPINTVL)err = -EINVAL;elsetp->keepalive_intvl = val * HZ;break;case TCP_KEEPCNT:			// 設置閾值次數if (val < 1 || val > MAX_TCP_KEEPCNT)err = -EINVAL;elsetp->keepalive_probes = val;break;release_sock(sk);return err;
}

這里非常的簡單,通過switch case的形式把參數添加到結構體中,并且設置了首次觸發的時間

接下來,我們看到定時器何時設置的。在net/ipv4/tcp_ipv4.c 文件中tcp_v4_init_sock方法。

static int tcp_v4_init_sock(struct sock *sk)
{…………tcp_init_xmit_timers(sk);…………return 0;
}void tcp_init_xmit_timers(struct sock *sk)
{inet_csk_init_xmit_timers(sk, &tcp_write_timer, &tcp_delack_timer,&tcp_keepalive_timer);
}void inet_csk_init_xmit_timers(struct sock *sk,void (*retransmit_handler)(unsigned long),void (*delack_handler)(unsigned long),void (*keepalive_handler)(unsigned long))
{struct inet_connection_sock *icsk = inet_csk(sk);…………// 初始化sk->sk_timer,也即初始化timer_list// timer_list在內核是一個定時器的結構體init_timer(&sk->sk_timer);// 設置定時器的回調函數sk->sk_timer.function		     = keepalive_handler;…………
}

把大部分無關的代碼省略掉以后,源碼看起來非常的簡單,這里初始化了定時器,并且把定時器的回調函數設置成tcp_keepalive_timer,所以接下來,我們直接分析tcp_keepalive_timer方法即可。在net/ipv4/tcp_timer.c 文件中?tcp_keepalive_timer方法。

// 當達到keepalive設置的值以后回掉此方法。
static void tcp_keepalive_timer (unsigned long data)
{struct sock *sk = (struct sock *) data;struct inet_connection_sock *icsk = inet_csk(sk);struct tcp_sock *tp = tcp_sk(sk);__u32 elapsed;/* Only process if socket is not in use. */bh_lock_sock(sk);if (sock_owned_by_user(sk)) {// 這里很簡單,因為鎖的原因,所以需要重試。inet_csk_reset_keepalive_timer (sk, HZ/20);goto out;}// 4次揮手階段,而此時達到了保活的檢測,此時發送RST報文給對端,表示我要斷開了,然后釋放資源即可。if (sk->sk_state == TCP_FIN_WAIT2 && sock_flag(sk, SOCK_DEAD)) {if (tp->linger2 >= 0) {const int tmo = tcp_fin_time(sk) - TCP_TIMEWAIT_LEN;if (tmo > 0) {tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);goto out;}}tcp_send_active_reset(sk, GFP_ATOMIC);goto death;}// 如果KeepAlive沒有開啟,或者當前已經是關閉狀態if (!sock_flag(sk, SOCK_KEEPOPEN) || sk->sk_state == TCP_CLOSE)goto out;// 算出下次檢測的時間elapsed = keepalive_time_when(tp);// 此時正在發送報文,所以無須檢測,直接重置下次檢測的時間if (tp->packets_out || tcp_send_head(sk))goto resched;// 算出距離上一次ACK的相對時間elapsed = tcp_time_stamp - tp->rcv_tstamp;// 如果上一次ACK的相對時間 大于等于 設置的時間,那么就代表達到一次閾值if (elapsed >= keepalive_time_when(tp)) {// 查看是否達到次數閾值,達到閾值后直接發送RST報文給對方,然后關閉連接。if ((!tp->keepalive_probes && icsk->icsk_probes_out >= sysctl_tcp_keepalive_probes) ||(tp->keepalive_probes && icsk->icsk_probes_out >= tp->keepalive_probes)) {tcp_send_active_reset(sk, GFP_ATOMIC);tcp_write_err(sk);goto out;}// 沒達到閾值的情況// 嘗試發送報文給對方,看是否還活著if (tcp_write_wakeup(sk) <= 0) {// 如果回復了,那就把下次檢測的時間設置好icsk->icsk_probes_out++;elapsed = keepalive_intvl_when(tp);} else {		// 對端沒有回復,不知道是因為丟失還是怎么了,所以加快速度,嘗試下一次。elapsed = TCP_RESOURCE_PROBE_INTERVAL;}} else {// 沒有達到上次ACK的相對時間,所以算出差值,設置到定時器中。elapsed = keepalive_time_when(tp) - elapsed;}TCP_CHECK_TIMER(sk);sk_stream_mem_reclaim(sk);resched:// 把最新值設置到定時器中。inet_csk_reset_keepalive_timer (sk, elapsed);goto out;death:// 關閉連接,釋放資源。tcp_done(sk);out:bh_unlock_sock(sk);sock_put(sk);
}

此方法是當定時器結束后回調執行,檢測是否達到了我們設置或者默認的閾值,如果沒有達到,再設置下一次定時器的時間,如果達到了就發送RST報文,關閉連接,釋放資源~!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/41916.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/41916.shtml
英文地址,請注明出處:http://en.pswp.cn/news/41916.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

高級AI賦能Fortinet FortiXDR解決方案

擴展檢測和響應 (XDR&#xff1a;Extended Detection and Response) 解決方案旨在幫助組織整合分布式安全技術&#xff0c;更有效地識別和響應活動的威脅。雖然 XDR 是一種新的技術概念&#xff0c;但其構建基礎是端點檢測和響應 (EDR&#xff1a;Endpoint Detection and Respo…

代碼隨想錄算法訓練營第50天|動態規劃part11

8.16周三 123.買賣股票的最佳時機III 188.買賣股票的最佳時機IV 詳細布置 123.買賣股票的最佳時機III 題目&#xff1a;最多買賣兩次 題解&#xff1a; 1、 dp[i][0]沒有操作 &#xff08;其實我們也可以不設置這個狀態&#xff09; dp[i][1]第一次持有股票 dp[i][2]第一…

CSDN?索尼 toio?應用創意開發征集征集活動 創意公示! 入選的用戶看過來~

索尼toio?應用創意開發征集活動自開啟以來&#xff0c;收到了很多精彩的創意&#xff01;接下來&#xff0c;我們將公示入選的20個優秀創意和10個入圍創意&#xff0c;以下提到ID的小伙伴注意啦&#xff0c;你們將有機會順利進入活動的第二階段&#xff0c;注意查收你們的信箱…

javaScript:快樂學習計時器

目錄 一.前言 二.計時器 1.計時器的分類 2. 創建計時器的方式 創建間隔計時器 創建方式三種 1.匿名函數 2.使用函數直接作為計時器的執行函數 2.使用函數直接作為計時器的執行函數,用字符串的形式寫入 3.計時器的返回值 4.清除計時器 5.延遲計時器 相關代碼 一.前言 在…

Linux--實用指令與方法(部分)

下文主要是一些工作中零碎的常用指令與方法 實用指令與方法&#xff08;部分&#xff09; linux長時間保持ssh連接 這個問題的原因是&#xff1a;設置檢測時間太短&#xff0c;或者沒有保持tcp長連接。 解決步驟&#xff1a; 步驟1&#xff1a;打開sshd配置文件&#xff0…

nbcio-boot從3.0升級到3.1的出現用戶管理與數據字典bug

升級后出現 系統管理里的用戶管理出現下面問題 2023-08-17 09:44:38.902 [http-nio-8080-exec-4] [1;31mERROR[0;39m [36mo.jeecg.common.exception.JeecgBootExceptionHandler:69[0;39m - java.lang.String cannot be cast to java.lang.Long java.lang.ClassCastException:…

【JS 線性代數算法之向量與矩陣】

線性代數算法 一、向量的加減乘除1. 向量加法2. 向量減法3. 向量數乘4. 向量點積5. 向量叉積 二、矩陣的加減乘除1. 矩陣加法2. 矩陣減法3. 矩陣數乘4. 矩陣乘法 常用數學庫 線性代數是數學的一個分支&#xff0c;用于研究線性方程組及其解的性質、向量空間及其變換的性質等。在…

windows bat腳本,使用命令行增加/刪除防火墻:入站-出站,規則

常常手動設置防火墻的入站或出站規則&#xff0c;比較麻煩&#xff0c;其實可以用命令行搞定。 下面是禁用BCompare.exe連接網絡的例子&#xff1a; ECHO OFF&(PUSHD "%~DP0")&(REG QUERY "HKU\S-1-5-19">NUL 2>&1)||(powershell -Comm…

web即時通訊系統與APP即時通訊系統有什么區別?

隨著互聯網的不斷發展&#xff0c;即時通訊技術也在不斷地完善和發展&#xff0c;其中Web即時通訊系統和APP即時通訊系統成為了人們廣泛使用的兩種通訊方式。那么&#xff0c;這兩者之間究竟有什么區別呢&#xff1f;在本文中&#xff0c;我們將為您詳細介紹這兩種通訊方式的區…

如何將labelImg打包成exe

最近整理一下數據標注這塊的內容&#xff0c;在目標檢測和目標分割里面用的最多的標注工具labelimg&#xff0c;labelme labelimg主要用于目標檢測領域制作自己的數據集&#xff0c;如&#xff1a;YOLO系列目標檢測模型 labelme主要用于圖像分割領域制作自己的數據集&#xf…

如何仿寫簡易tomcat 實現思路+代碼詳細講解

仿寫之前&#xff0c;我們要搞清楚都要用到哪些技術 自定義注解&#xff0c;比如Tomcat使用的是Servlet&#xff0c;我們可以定義一個自己的MyServlet構造請求體和返回體&#xff0c;比如tomcat使用HttpRequest&#xff0c;我們可以自己定義myHttpRequestjava去遍歷一個指定目…

Structs新增接口 報錯404,找不到資源

起因&#xff1a;最近在一個古老框架structs上開發新功能&#xff0c;由于之前沒接觸過&#xff0c;故此記錄 新增接口&#xff0c; 接口類&#xff1a; Path("/A") Produces({ MediaType.APPLICATION_JSON }) public interface Money {POSTPath("/B")Resu…

數據結構——鏈表詳解

鏈表 文章目錄 鏈表前言認識鏈表單鏈表結構圖帶頭單循環鏈表結構圖雙向循環鏈表結構圖帶頭雙向循環鏈表結構圖 鏈表特點 鏈表實現(帶頭雙向循環鏈表實現)鏈表結構體(1) 新建頭節點(2) 建立新節點(3)尾部插入節點(4)刪除節點(5)頭部插入節點(6) 頭刪節點(7) 尋找節點(8) pos位置…

網絡編程socket.close/output.close/socket.shutdownOutput區別與流程分析

文章目錄 三種方法效果的區別套接字Socket關閉與釋放的區別服務器執行三種關閉操作后&#xff0c;繼續發送/接收數據會發生什么socket.shutdownOutput 關閉連接 找了半個小時沒一個說明白的帖子&#xff0c;真的折磨 三種方法效果的區別 socket.close()Socket主動禁止輸入和輸…

APP外包開發原生和H5的區別

原生開發和H5開發是兩種不同的方法&#xff0c;用于創建移動應用程序。它們具有各自的特點、優勢和劣勢&#xff0c;適用于不同的應用場景。以下是原生開發和H5開發之間的一些主要區別&#xff0c;希望對大家有所幫助。北京木奇移動技術有限公司&#xff0c;專業的軟件外包開發…

DELETE 與TRUNCATE區別

DELETE 與TRUNCATE區別 要清空 PostgreSQL 中的表數據&#xff0c;可以使用 DELETE 或 TRUNCATE 語句。下面是兩種方法的示例&#xff1a; 使用 DELETE 語句清空表數據&#xff1a; DELETE FROM 表名;例如&#xff0c;要清空名為 users 的表數據&#xff1a; DELETE FROM u…

未來公文的智能化進程

隨著技術的飛速發展&#xff0c;公文——這個有著悠久歷史的官方溝通方式&#xff0c;也正逐步走向智能化的未來。自動化、人工智能、區塊鏈...這些現代科技正重塑我們的公文制度&#xff0c;讓其變得更加高效、安全和智慧。 1.語義理解與自動生成 通過深度學習和NLP&#xff…

14-案例:購物車

綜合案例-購物車 需求說明: 1. 渲染功能 v-if/v-else v-for :class 2. 刪除功能 點擊傳參 filter過濾覆蓋原數組 3. 修改個數 點擊傳參 find找對象 4. 全選反選 計算屬性computed 完整寫法 get/set 5. 統計 選中的 總價 和 數量 計算屬性conputed reduce條件求和 6. 持久化到本…

電子商務公開密鑰加密法

(一)定義與應用原理 公開密鑰加密法是針對私有密鑰加密法的缺陷而提出來的。是電子商務應 用的核心密碼技術。所謂公開密鑰加密&#xff0c;就是指在計算機網絡上甲、乙兩用戶之間 進行通信時&#xff0c;發送方甲為了保護要傳輸的明文信息不被第三方竊取&#xff0c;采用密…

從零基礎到精通IT:探索高效學習路徑與成功案例

文章目錄 導語&#xff1a;第一步&#xff1a;明確學習目標與方向選擇適合的IT方向設定具體的學習目標咨詢和調研 第二步&#xff1a;系統學習基礎知識選擇適合的編程語言學習數據結構和算法掌握操作系統和計算機網絡基礎 第三步&#xff1a;實踐項目鍛煉技能選擇合適的項目編寫…