日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

TCP的定时器系列 — 超时重传定时器(有图有代码有真相!!!)

發(fā)布時(shí)間:2023/12/20 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 TCP的定时器系列 — 超时重传定时器(有图有代码有真相!!!) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

轉(zhuǎn)載

主要內(nèi)容:TCP定時(shí)器概述,超時(shí)重傳定時(shí)器、ER延遲定時(shí)器、PTO定時(shí)器的實(shí)現(xiàn)。

內(nèi)核版本:3.15.2

我的博客:http://blog.csdn.net/zhangskd

?

Q:一條TCP連接會(huì)使用多少個(gè)定時(shí)器呢?

A:目前的答案是9個(gè):

超時(shí)重傳定時(shí)器,持續(xù)定時(shí)器,ER延遲定時(shí)器,PTO定時(shí)器,ACK延遲定時(shí)器,

SYNACK定時(shí)器,?;疃〞r(shí)器,FIN_WAIT2定時(shí)器,TIME_WAIT定時(shí)器。

?

數(shù)據(jù)結(jié)構(gòu)

?

幾種定時(shí)器的標(biāo)識(shí):

#define ICSK_TIME_RETRANS 1 /* Retransmit timer */

#define ICSK_TIME_DACK 2 /* Delayed ack timer */

#define ICSK_TIME_PROBE0 3 /* Zero window probe timer */

#define ICSK_TIME_EARLY_RETRANS 4 /* Early retransmit timer */

#define ICSK_TIME_LOSS_PROBE 5 /* Tail loss probe timer */

?

上述5種定時(shí)器分別為:超時(shí)重傳定時(shí)器、ACK延遲定時(shí)器、持續(xù)定時(shí)器、ER延遲定時(shí)器、PTO定時(shí)器。

另外還有?;疃〞r(shí)器、FIN_WAIT2定時(shí)器、TIME_WAIT定時(shí)器、SYNACK定時(shí)器。

?

雖然定義了9個(gè)定時(shí)器,但是內(nèi)核中只用了4個(gè)實(shí)例(timer_list),所以有些定時(shí)器是共用一個(gè)實(shí)例的。

這4個(gè)實(shí)例分別是:

icsk->icsk_retransmit_timer:超時(shí)重傳定時(shí)器、持續(xù)定時(shí)器、ER延遲定時(shí)器、PTO定時(shí)器。

icsk->icsk_delack_timer:ACK延遲定時(shí)器。

sk->sk_timer:?;疃〞r(shí)器,SYNACK定時(shí)器,FIN_WAIT2定時(shí)器。

death_row->tw_timer:TIME_WAIT定時(shí)器。

?

創(chuàng)建和刪除

?

(1) 定時(shí)器的創(chuàng)建

tcp_v4_init_sock

??? |-> tcp_init_sock

???????????? |-> tcp_init_xmit_timers

??????????????????????? |-> inet_csk_init_xmit_timers

?

在初始化連接時(shí),設(shè)置三個(gè)定時(shí)器實(shí)例的處理函數(shù):

icsk->icsk_retransmit_timer的處理函數(shù)為tcp_write_timer()

icsk->icsk_delack_timer的處理函數(shù)為tcp_delack_timer()

sk->sk_timer的處理函數(shù)為tcp_keepalive_timer()

void tcp_init_xmit_timers(struct sock *sk) {inet_csk_init_xmit_timers(sk, &tcp_write_timer, &tcp_delack_timer, &tcp_keepalive_timer); } /** Using different timers for retransmit, delayed acks and probes.* We may wish use just one timer maintaining a list of expire jiffies to optimize.*/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_timer, (unsigned long)sk);setup_timer(&sk->sk_timer, keepalive_handler, (unsigned long)sk);icsk->icsk_pending = icsk->icsk_ack.pending = 0; }

?

(2) 定時(shí)器的刪除

tcp_done

tcp_disconnect

tcp_v4_destroy_sock

??? |-> tcp_clear_xmit_timers

????????????? |-> inet_csk_clear_xmit_timers

void inet_csk_clear_xmit_timers(struct sock *sk) {struct inet_connection_sock *icsk = inet_csk(sk);icsk->icsk_pending = icsk->icsk_ack.pending = icsk->icsk_ack.blocked = 0;sk_stop_timer(sk, &icsk->icsk_retransmit_timer);sk_stop_timer(sk, &icsk->icsk_delack_timer);sk_stop_timer(sk, &sk->sk_timer); }

?

激活

?

icsk->icsk_retransmit_timer和icsk->icsk_delack_timer的激活函數(shù)為inet_csk_reset_xmit_timer(),

共負(fù)責(zé)了5個(gè)定時(shí)器的激活工作。

/** Reset the retransmissiion timer*/ static inline void inet_csk_reset_xmit_timer(struct sock *sk, const int what,unsigned long when,const unsigned long max_when) {struct inet_connection_sock *icsk = inet_csk(sk);if (when > max_when) { #ifdef INET_CSK_DEBUGpr_debug("reset_xmit_timer: sk=%p %d when=0x%lx, caller=%p\n",sk, what, when, current_text_addr()); #endifwhen = max_when;}if (what == ICSK_TIME_RETRANS || what == ICSK_TIME_PROBE0 || what == ICSK_TIME_EARLY_RETRANS || what == ICSK_TIME_LOSS_PROBE) {icsk->icsk_pending = what;icsk->icsk_timeout = jiffies + when; /*數(shù)據(jù)包超時(shí)時(shí)刻*/sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout);} else if (what == ICSK_TIME_DACK) {icsk->icsk_ack.pending |= ICSK_ACK_TIMER;icsk->icsk_ack.timeout = jiffies + when; /*Delay ACK定時(shí)器超時(shí)時(shí)刻*/sk_reset_timer(sk, &icsk->icsk_delack_timer, icsk->icsk_ack.timeout);} #ifdef INET_CSK_DEBUGelse {pr_debug("%s", inet_csk_timer_bug_msg);} #endif }

其中,超時(shí)重傳定時(shí)器(ICSK_TIME_RETRANS)在以下幾種情況下會(huì)被激活:

1. 發(fā)現(xiàn)對(duì)端把保存在接收緩沖區(qū)的SACK段丟棄時(shí)。

2. 發(fā)送一個(gè)數(shù)據(jù)段時(shí),發(fā)現(xiàn)之前網(wǎng)絡(luò)中不存在發(fā)送且未確認(rèn)的段。

??? 之后每當(dāng)收到確認(rèn)了新數(shù)據(jù)段的ACK,則重置定時(shí)器。

3. 發(fā)送SYN包后。

4. 一些特殊情況。

?

超時(shí)處理函數(shù)

?

當(dāng)icsk->icsk_retransmit_timer超時(shí)后,會(huì)調(diào)用其處理函數(shù)tcp_write_timer()進(jìn)行處理。

staic void tcp_write_timer(unsigned long data) {struct sock *sk = (struct sock *)data;bh_lock_sock(sk);if (! sock_owned_by_user(sk)) { /* sk沒被用戶空間占用 */tcp_write_timer_handler(sk);} else { /* 否則先設(shè)置延遲標(biāo)志,之后再處理 *//* delegate our work to tcp_release_cb() */if (! test_and_set_bit(TCP_WRITE_TIMER_DEFERRED, &tcp_sk(sk)->tsq_flags))sock_hold(sk);}bh_unlock_sock(sk);sock_put(sk); }


icsk->icsk_retransmit_timer可同時(shí)作為:超時(shí)重傳定時(shí)器、持續(xù)定時(shí)器、ER延遲定時(shí)器、PTO定時(shí)器,

所以需要判斷是哪種定時(shí)器觸發(fā)的,然后采取相應(yīng)的處理措施。

void tcp_write_timer_handler(struct sock *sk) {struct inet_connection_sock *icsk = inet_csk(sk);int event;/* 如果連接處于CLOSED狀態(tài),或者沒有定時(shí)器在計(jì)時(shí) */if (sk->sk_state == TCP_CLOSE || !icsk->icsk_pending)goto out;/* 如果定時(shí)器還沒有超時(shí),那么繼續(xù)計(jì)時(shí) */if (time_after(icsk->icsk_timeout, jiffies)) {sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout);goto out;}event = icsk->icsk_pending; /* 用于表明是哪種定時(shí)器 */switch(event) {case ICSK_TIME_EARLY_RETRANS: /* ER延遲定時(shí)器觸發(fā)的 */tcp_resume_early_retransmit(sk); /* 進(jìn)行early retransmit */break;case ICSK_TIME_LOSS_PROBE: /* PTO定時(shí)器觸發(fā)的 */tcp_send_loss_probe(sk); /* 發(fā)送TLP探測(cè)包 */break;case ICSK_TIME_RETRANS: /* 超時(shí)重傳定時(shí)器觸發(fā)的 */icsk->icsk_pending = 0;tcp_retransmit_timer(sk);break;case ICSK_TIME_PROBE0: /* 持續(xù)定時(shí)器觸發(fā)的 */icsk->icsk_pending = 0;tcp_probe_timer(sk);break;}out:sk_mem_reclaim(sk); }

?

如果是超時(shí)重傳定時(shí)器觸發(fā)的,就會(huì)調(diào)用tcp_retransmit_timer()進(jìn)行處理。?

void tcp_retransmit_timer(struct sock *sk) {struct tcp_sock *tp = tcp_sk(sk);struct inet_connection_sock *icsk = inet_csk(sk);/* Fast Open特性相關(guān),暫時(shí)不做分析 */if (tp->fastopen_rsk) {...}/* 如果沒有發(fā)送且未確認(rèn)的數(shù)據(jù)段,沒有等待哪來的超時(shí),直接返回 */if (! tp->packets_out)goto out; /* 如果發(fā)送隊(duì)列為空,顯然不合理 */WARN_ON(tcp_write_queue_empty(sk));/* 發(fā)生RTO超時(shí)表明數(shù)據(jù)包的確丟失了,所以不用再進(jìn)行額外檢測(cè)了 */tp->tlp_high_seq = 0;/* 如果對(duì)端通告窗口為0,sk不處于DEAD狀態(tài),TCP連接不處于三次握手 *//* Receiver dastardly shrinks window. Our retransmits become zero probes,* but we should not timeout this connection. If the socket is an orphan, time it out,* we cannot allow such beasts to hang infinitely. */if (! tp->snd_wnd && ! sock_flag(sk, SOCK_DEAD) && ! ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV))) {struct inet_sock *inet = inet_sk(sk);if (sk->sk_family == AF_INET) {LIMIT_NETDEBUG(KERN_DEBUG pr_fmt("Peer %pI4:%u/ %u unexpectedly shrunk window %u:%u (repaired)\n",&inet->inet_daddr, ntohs(inet->inet_dport), inet->inet_num, tp->snd_una, tp->snd_nxt);}#if IS_ENABLED(CONFIG_IPV6)/* IPv6的處理,此處省略 */... #endif/* 距離上次收到ACK的時(shí)間超過了最大超時(shí)時(shí)間,認(rèn)為有錯(cuò)誤發(fā)生了*/if (tcp_time_stamp - tp->rcv_tstamp > TCP_RTO_MAX) {tcp_write_err(sk); /* 報(bào)告錯(cuò)誤,調(diào)用tcp_done終止連接 */goto out;}/* 進(jìn)入Loss狀態(tài),標(biāo)志丟失的數(shù)據(jù)段 */tcp_enter_loss(sk, 0);/* 重傳發(fā)送隊(duì)列的第一個(gè)數(shù)據(jù)段 */tcp_retransmit_skb(sk, tcp_write_queue_head(sk));__sk_dst_reset(sk); /* 更新路由緩存 */goto out_reset_timer;}/* 如果重傳次數(shù)達(dá)到上限,報(bào)告錯(cuò)誤并關(guān)閉套接口。* 如果資源使用達(dá)到上限,放棄本次重傳。*/if (tcp_write_timeout(sk))goto out;/* 剛進(jìn)入超時(shí)重傳 */if (icsk->icsk_retransmits == 0) {int mib_idx;/* Recovery狀態(tài)中發(fā)生超時(shí) */if (icsk->icsk_ca_state == TCP_CA_Recovery) {if (tcp_is_sack(tp))mib_idx = LINUX_MIB_TCPSACKRECOVERYFAIL;elsemib_idx = LINUX_MIB_TCPRENORECOVERYFAIL;} else if (icsk->icsk_ca_state == TCP_CA_Loss) { /* Loss狀態(tài)中發(fā)生超時(shí) */ mib_idx = LINUX_MIB_TCPLOSSFAILURES;} else if (icsk->icsk_ca_state == TCP_CA_Disorder) || tp->sacked_out) {/* Disorder狀態(tài)中發(fā)生超時(shí) */if (tcp_is_sack(tp))mib_idx = LINUX_MIB_TCPSACKFAILURES;elsemib_idx = LINUX_MIB_TCPRENOFAILURES;} else { /* Open狀態(tài)或CWR狀態(tài)中發(fā)生超時(shí) */mib_idx = LINUX_MIB_TCPTIMEOUTS;}NET_INC_STATS_BH(sock_net(sk), mib_idx);}/* 進(jìn)入Loss狀態(tài),標(biāo)志丟失的數(shù)據(jù)段。* 值得注意的是不再使用tcp_enter_frto_loss(),FRTO機(jī)制被重寫了。*/tcp_enter_loss(sk, 0);/* 重傳發(fā)送隊(duì)列的第一個(gè)數(shù)據(jù)段,如果失敗說明是本地?fù)砣?#xff0c;那么不進(jìn)行指數(shù)退避 */if (tcp_retransmit_skb(sk, tcp_write_queue_head(sk)) > 0) {/* Retransmission failed because of local congestion, do not backoff. */if (! icsk->icsk_retransmits)icsk->icsk_retransmits = 1;inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, min(icsk->icsk_rto, TCP_RESOURCE_PROBE_INTERVAL),TCP_RTO_MAX);goto out;}icsk->icsk_backoff++; /* 退避指數(shù),采集到新的RTT樣本時(shí)清零 */icsk->icsk_retransmits++; /* 超時(shí)次數(shù),當(dāng)確認(rèn)了新數(shù)據(jù)時(shí)清零 */out_reset_timer:/* 對(duì)于符合條件的Thin stream不使用指數(shù)退避,重復(fù)超時(shí)6次后除外(太慘了:) */if (sk->sk_state == TCP_ESTABLISHED && (tp->thin_lto || sysctl_tcp_thin_linear_timeouts) &&tcp_stream_is_thin(tp) && icsk->icsk_retransmits <= TCP_THIN_LINEAR_RETRIES) {icsk->icsk_backoff = 0;icsk->icsk_rto = min(__tcp_set_rto(tp), TCP_RTO_MAX); /* RTO保持原樣 */} else { /* Use normal (exponential) backoff */icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX); /* RTO翻倍 */}/* 重置超時(shí)定時(shí)器,就是上文說的激活 */inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, icsk->icsk_rto, TCP_RTO_MAX);/* 當(dāng)重傳多次時(shí),需要根據(jù)重傳時(shí)間間隔,決定是否更新路由緩存 */if (retransmits_timed_out(sk, sysctl_tcp_retries1 + 1, 0, 0))__sk_dst_reset(sk);out:; }

?

連接的超時(shí)

?

很明顯,重傳不能無限的進(jìn)行下去,當(dāng)重傳的次數(shù)超過設(shè)定的上限時(shí),就會(huì)判定連接超時(shí),關(guān)閉該連接。

此后相應(yīng)的socket函數(shù),比如connect和send,就會(huì)返回-1,errno設(shè)為ETIMEDOUT,表示連接超時(shí)。

?

判定連接是否超時(shí),如果超過了最大等待時(shí)間,就放棄此連接。

/* A write timeout has occurred. Process the after effects. */ static int tcp_write_timeout (struct sock *sk) {struct inet_connection_sock *icsk = inet_csk(sk);struct tcp_sock *tp = tcp_sk(sk);int retry_until;bool do_reset, syn_set = false;/* 如果超時(shí)是發(fā)生在三次握手期間 */if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {if (icsk->icsk_retransmits) { /* 如果之前重傳過了 */dst_negative_advice(sk); /* 更新目的路由緩存 *//* SYN攜帶Fast Open選項(xiàng),或SYN攜帶數(shù)據(jù) */if (tp->syn_fastopen || tp->syn_data)tcp_fastopen_cache_set(sk, 0, NULL, true);if (tp->syn_data)NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPFASTOPENACTIVEFAIL);}/* SYN的最大重傳次數(shù),由TCP_SYNCNT選項(xiàng)和tcp_syn_retries參數(shù)決定,默認(rèn)是5次 */retry_until = icsk->icsk_syn_retries ?: sysctl_tcp_syn_retries;syn_set = true;} else {/* tcp_retries1默認(rèn)為3,當(dāng)重傳次數(shù)超過此值時(shí),表示可能遇到了黑洞,需要進(jìn)行PMTU* 檢測(cè),同時(shí)更新路由緩存。*/if (retransmits_timed_out(sk, sysctl_tcp_retries1, 0, 0)) {/* Black hole detection */tcp_mtu_probing(icsk, sk); /* PMTU檢測(cè) */dst_negative_advic(sk); /* 更新路由緩存 */}retry_until = sysctl_tcp_retries2; /* 在斷開TCP連接之前,最多進(jìn)行多少次重傳,默認(rèn)值為15 *//* 如果當(dāng)前套接口即將關(guān)閉 */if (sock_flag(sk, SOCK_DEAD)) {const int alive = (icsk->icsk_rto < TCP_RTO_MAX);retry_until = tcp_orphan_retries(sk, alive); /* 決定重傳次數(shù) */do_reset = alive || ! retransmits_timed_out(sk, retry_until, 0, 0);/* 如果當(dāng)前的孤兒socket數(shù)量超過tcp_max_orphans,或者內(nèi)存不夠時(shí),關(guān)閉此連接 */if (tcp_out_of_resources(sk, do_reset))return 1;}}/* 判定連接是否等待過久,即是否超時(shí) */if (retransmits_timed_out(sk, retry_until, syn_set ? 0 : icsk->icsk_user_timeout, syn_set)) {/* Has it gone just too far ? */tcp_write_err(sk);return 1;} }/* Calculate maximal number or retries on an orphaned socket. */ static int tcp_orphan_retries (struct sock *sk, int alive) {int retries = sysctl_tcp_orphan_retries; /* May be zero. *//* We know from an ICMP that something is wrong. */if (sk->sk_err_soft && !alive)retries = 0;/* However, if socket sent something recently, select some safe number of retries.* 8 corresponds to > 100 seconds with minimal RTO of 200msec.*/if (retries == 0 && alive)retries = 8;return retries; }

?

判斷連接是否超時(shí),要分為3種情況。

1. SYN包:當(dāng)SYN包的重傳次數(shù)達(dá)到上限時(shí),判定連接超時(shí)。(默認(rèn)允許重傳5次,初始超時(shí)時(shí)間為1s,總共歷時(shí)31s)

2. 非SYN包,用戶使用TCP_USER_TIMEOUT:當(dāng)數(shù)據(jù)包發(fā)出去后的等待時(shí)間超過用戶設(shè)置的時(shí)間時(shí),判定連接超時(shí)。

3. 非SYN包,用戶沒有使用TCP_USER_TIMEOUT:當(dāng)數(shù)據(jù)包發(fā)出去后的等待時(shí)間超過以TCP_RTO_MIN為初始超時(shí)

時(shí)間,重傳boundary次所花費(fèi)的時(shí)間后,判定連接超時(shí)。

?

如果返回值為真,判定連接超時(shí),則關(guān)閉連接,把errno設(shè)置為ETIMEDOUT,Socket函數(shù)返回-1。

boundary為最大重傳次數(shù),timeout為用戶設(shè)置的超時(shí)時(shí)間。(通過TCP_USER_TIMEOUT選項(xiàng)設(shè)置)

struct tcp_sock {.../* Timestamp of the last retransmit, also used in SYN-SENT to remember stamp of* the first SYN.*//* 上面的注釋是錯(cuò)誤的,變量的含義為:* 1. 原始SYN包的發(fā)送時(shí)間點(diǎn),不是重傳的。* 2. 本次重傳時(shí),第一個(gè)被重傳的包,原始包的發(fā)送時(shí)間點(diǎn)。* 這個(gè)變量是用來計(jì)算連接的超時(shí)關(guān)閉時(shí)間:一個(gè)包發(fā)送多久還沒有得到響應(yīng),就判斷連接超時(shí)。*/u32 retrans_stamp; ... };/* This function calculates a "timeout" which is equivalent to the timeout of a TCP connection* after " boundary unsuccessful, exponentially backed-off retransmissions with an initial RTO* of TCP_RTO_MIN or TCP_TIMEOUT_INIT if syn_set flag is set.*/ static bool retransmits_timed_out (struct sock *sk, unsigned int boundary, unsigned int timeout,bool syn_set) {unsigned int linear_backoff_thresh, start_ts;/* 如果是SYN包,則默認(rèn)的初始超時(shí)時(shí)間為1s,否則為200ms。*/unsigned int rto_base = syn_set ? TCP_TIMEOUT_INIT : TCP_RTO_MIN;if (! inet_csk(sk)->icsk_retransmits))return false; /* 如果之前沒有重傳過,直接返回false *//* start_ts為開始計(jì)時(shí)點(diǎn),注意是原始包第一次被發(fā)送時(shí)的時(shí)間戳,不是重傳包的 */if (unlikely(! tcp_sk(sk)->retrans_stamp)) start_ts = TCP_SKB_CB(tcp_write_queue_head(sk))->when;elsestart_ts = tcp_sk(sk)->retrans_stamp;/* 包括兩種情況:* SYN包:timeout始終設(shè)置為0,因?yàn)樵谌挝帐謺r(shí)TCP_USER_TIMEOUT是無效的。* 非SYN包:用戶沒有使用TCP_USER_TIMEOUT選項(xiàng)時(shí)。*/if (likely(timeout == 0)) { /* 當(dāng)rto不超過最大值時(shí),能夠進(jìn)行多少次指數(shù)退避。* 以SYN包為例,rto_base為1s,此值向上取整為7。*/linear_backoff_thresh = ilog2(TCP_RTO_MAX/rto_base); /* 從計(jì)時(shí)點(diǎn)開始,經(jīng)過boundary次重傳后,總共花費(fèi)的時(shí)間 */if (boundary <= linear_backoff_thresh)timeout = ((2 << boundary) -1) * rto_base; elsetimeout = ((2 << linear_backoff_thresh) -1) * rto_base + (boundary - linear_backoff_thresh) * TCP_RTO_MAX;}

return (tcp_time_stamp - start_ts) >= timeout; /* 判斷等待時(shí)間是否過長,即是否要放棄連接 */

總結(jié)

以上是生活随笔為你收集整理的TCP的定时器系列 — 超时重传定时器(有图有代码有真相!!!)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。