Linux下關於TCP的keep alive的實現源碼分析

來源:互聯網
上載者:User

標籤:

 TCP下的Keep Alive

我們常說的TCP的keep alive,就是為了保證串連的有效性,在間隔一定的時間發探測包,根據回複來確認該串連是否有效。通常上層應用會自己提供心跳檢測機制,而Linux核心本身也提供了從核心層面的確保串連有效性的方式。

在sock 函數中可以設定是否需要開啟keep alive開關,預設建立socket 是關閉keep alive的。代碼如下

  optval = 1;   optlen = sizeof(optval);   if(setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) {      perror("setsockopt()");      close(s);      exit(EXIT_FAILURE);   }

Keep Alive 的控制參數

tcp_keepalive_time 參數控制keep alive的最長空閑時間
tcp_keepalive_probes 參數

當超過最長空間時間後,核心會嘗試發出探測包確認用戶端時候存活,該參數控制的是嘗試的次數

tcp_keepalive_intvl 參數當超過最長空閑時間後,核心會發出探測包,當沒有收到確認回複的,該參數控制下個探測包的時間

Linux下如何?Keep Alivesock結構體中的timer_list

在sock結構體中,存在timer_list的結構體sk_timer,參考下面結構

struct sock{...struct timer_listsk_timer;...}
struct timer_list {struct list_head entry;unsigned long expires;void (*function)(unsigned long);unsigned long data;struct tvec_base *base;#ifdef CONFIG_TIMER_STATSvoid *start_site;char start_comm[16];int start_pid;#endif#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;#endif};
timer_list結構體,是在sock裡常用的timer執行鏈表,entry代表的是鏈表的頭, expires代表的失效時間,而function就是執行的函數。

註冊keepalive處理函數

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);setup_timer(&icsk->icsk_retransmit_timer, retransmit_handler,(unsigned long)sk);setup_timer(&icsk->icsk_delack_timer, delack_handler,(unsigned long)sk);setup_timer(&sk->sk_timer, keepalive_handler, (unsigned long)sk);//註冊了函數在sk_timer中icsk->icsk_pending = icsk->icsk_ack.pending = 0;}

當串連完成的時候(也就是握手成功的時候),在新產生的sock裡面的sk_timer結構體中,註冊了函數keepalive_handler函數

void tcp_init_xmit_timers(struct sock *sk){inet_csk_init_xmit_timers(sk, &tcp_write_timer, &tcp_delack_timer,  &tcp_keepalive_timer);}

而keepalive_handler函數就是tcp_keepalive_timer函數

static void tcp_keepalive_timer (unsigned long data){......if (!sock_flag(sk, SOCK_KEEPOPEN) || sk->sk_state == TCP_CLOSE)goto out;elapsed = keepalive_time_when(tp);/* It is alive without keepalive 8) */if (tp->packets_out || tcp_send_head(sk))goto resched;elapsed = tcp_time_stamp - tp->rcv_tstamp;if (elapsed >= keepalive_time_when(tp)) {if (icsk->icsk_probes_out >= keepalive_probes(tp)) {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 {/* If keepalive was lost due to local congestion, * try harder. */elapsed = TCP_RESOURCE_PROBE_INTERVAL;}} else {/* It is tp->rcv_tstamp + keepalive_time_when(tp) */elapsed = keepalive_time_when(tp) - elapsed;}         .....}

上面只是截取了一部分代碼,重點是前面提到的參數的實現,代碼首先先檢查了是否在sock裡設定了參數SO_KEEPALIVE,也就是sock裡面的flag:SOCK_KEEPOPEN。

如果設定了socket的SO_KEEPALIVE,才繼續檢查時間戳記,取的上次收到包的時間戳記和目前時間戳的差值,進行和參數keepalive_time的比較,如果已經逾時了,那麼檢查發已經出探測包失敗的次數,如果次數已經比keepalive_probes的大,那麼發出reset包,同時寫錯誤報表,關閉sock。

 如果比設定的探測包次數小的話,那發出探測包,同時設定下次的校正的時間戳記為keepalive_intvl, 而不在是keepalive_time。

注意:在這裡keepalive_intvl只是控制觸發下次校正的時間

計算結束無效串連的時間N會有兩種情況

a.  keepalive_intvl 的時間比 keepalive_time 大

N=keepalive_time +keepalive_intvl*keepalive_probes

b. keepalive_intvl 的時間比 keepalive_time小

N=keepalive_time +keepalive_time*keepalive_probes

這也就是為什麼在預設設定裡,認為無效的串連的時間實際上是7200*6 要12小時才會斷掉串連代碼中設定keepalive_time,keepalive_probes,keepalive_intvl
                     setsockopt(s, SOL_TCP, TCP_KEEPIDLE, &val, sizeof(int))                     setsockopt(s, SOL_TCP, TCP_KEEPINTVL, &val, sizeof(int))                     setsockopt(s, SOL_TCP, TCP_KEEPCNT, &val, sizeof(int))        

所對應的3個參數 TCP_KEEPIDLE  --> keepalive_time, TCP_KEEPINTVL--> keepalive_intvl, TCP_KEEPCNT--> keepalive_probes3個參數本身也有最大值的保護,TCP_KEEPIDLE 最大是32767 TCP_KEEPINTVL 最大值是32767 TCP_KEEPCNT 最大值是127
JAVA中並沒有提供對這些參數的修改

處理中的定時器而TCP串連過程中,會有很多的定時器timer,做一些定時的檢查,比如前面的部落格裡提到的清除accept queue的定時器,ack的定時器,有興趣的可以參考tcp_timer.c定時器的主要作用就是在固定時的狀態下進行程式調用,在keep alive中就是定時發送探測包以確定包的有效性。
JAVA設定Keep alive
Java 裡只允許開啟keep alive,但卻不允許設定keep alive的幾個相關參數,Java 對於用戶端中開啟keep alive直接調用Socket.setKeepAlive函數,而在伺服器端的ServerSocket 卻不允許設定keep alive的開關,你只能在accept 一個新的串連的socket 的時候設定。
如何能設定剛才的需要的參數在java中?這裡只是提一個思路
在java裡的最後JNI的調用中,設定了常量數組的保護,只有數組中的參數才被允許設定到socket中
const opts[] = {        { java_net_SocketOptions_TCP_NODELAY,           IPPROTO_TCP,    TCP_NODELAY },        { java_net_SocketOptions_SO_OOBINLINE,          SOL_SOCKET,     SO_OOBINLINE },        { java_net_SocketOptions_SO_LINGER,             SOL_SOCKET,     SO_LINGER },        { java_net_SocketOptions_SO_SNDBUF,             SOL_SOCKET,     SO_SNDBUF },        { java_net_SocketOptions_SO_RCVBUF,             SOL_SOCKET,     SO_RCVBUF },        { java_net_SocketOptions_SO_KEEPALIVE,          SOL_SOCKET,     SO_KEEPALIVE },        { java_net_SocketOptions_SO_REUSEADDR,          SOL_SOCKET,     SO_REUSEADDR },        { java_net_SocketOptions_SO_BROADCAST,          SOL_SOCKET,     SO_BROADCAST },        { java_net_SocketOptions_IP_TOS,                IPPROTO_IP,     IP_TOS },        { java_net_SocketOptions_IP_MULTICAST_IF,       IPPROTO_IP,     IP_MULTICAST_IF },        { java_net_SocketOptions_IP_MULTICAST_IF2,      IPPROTO_IP,     IP_MULTICAST_IF },        { java_net_SocketOptions_IP_MULTICAST_LOOP,     IPPROTO_IP,     IP_MULTICAST_LOOP },    };

直接設定是沒有辦法了,但可以用自己寫JNI的方法來添加,因為在socket裡有FileDescriptor fd,而裡面的int fd 就是對應到核心中socket的 fd, 只要拿到這個fd 就可以調用自己的native方法來設定需要的參數,當然你也可以寫自己的 socket, 來封裝一個寫setsocketoption的native 函數。

Linux下關於TCP的keep alive的實現源碼分析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.