1. 程式人生 > >Linux 建立 TCP 連線的超時時間分析(解惑)

Linux 建立 TCP 連線的超時時間分析(解惑)

Linux 系統預設的建立 TCP 連線的超時時間為 127 秒,對於許多客戶端來說,這個時間都太長了, 特別是當這個客戶端實際上是一個服務的時候,更希望能夠儘早失敗,以便能夠選擇其它的可用服務重新嘗試。

socket 是 Linux 下實現的傳輸控制層協議,包括 TCP 和 UDP,一個 socket 端點由 IP 和埠對來唯一標識; 如果開啟了地址複用,那麼可以進一步由協議,IP 和埠來唯一標識。

系統呼叫 connect(2) 則是用來嘗試建立 socket 連線(TCP)或者和遠端協商一致(UDP)的函式。 connect 對於 UDP 來說並不是必須的,而對於 TCP 來說則是一個必須過程,註明的 TCP 3 次握手實際上也由 connect 來完成。

注:這裡只分析 TCP 連線超時

網路中的連線超時非常常見,不管是廣域網還是區域網,為了一定程度上容忍失敗,所以連線加入了重試機制, 而另一方面,為了不給服務端帶來過大的壓力,重試也是有限制的。

在 Linux 中,連線超時典型為 2 分 7 秒,而對於一些 client 來說,這是一個非常長的時間; 所以在程式設計中,可以使用非阻塞的方式來實現,例如:使用 poll(2), epoll(2), select(2) 等系統呼叫來實現多路複用等待。

下面來看看 2 分 7 秒是怎樣來的,以及怎樣配置 Linux kernel 來縮短這個超時。

2 分 7 秒

2 分 7 秒即 127 秒,剛好是 2 的 7 次方減一,聰明的讀者可能已經看出來了,如果 TCP 握手的 SYN 包超時重試按照 2 的冪來 backoff, 那麼:

第 1 次傳送 SYN 報文後等待 1s(2 的 0 次冪),如果超時,則重試
第 2 次傳送後等待 2s(2 的 1 次冪),如果超時,則重試
第 3 次傳送後等待 4s(2 的 2 次冪),如果超時,則重試
第 4 次傳送後等待 8s(2 的 3 次冪),如果超時,則重試
第 5 次傳送後等待 16s(2 的 4 次冪),如果超時,則重試
第 6 次傳送後等待 32s(2 的 5 次冪),如果超時,則重試
第 7 次傳送後等待 64s(2 的 6 次冪),如果超時,則超時失敗

上面的結果剛好是 127 秒。也就是說 Linux 核心在嘗試建立 TCP 連線時,最多會嘗試 7 次。

那麼下面通過具體方法來驗證。

首先,配置 iptables 來丟棄指定埠的 SYN 報文

# iptables -A INPUT --protocol tcp --dport 5000 --syn -j DROP

然後,開啟 tcpdump 觀察到達指定埠的報文

# tcpdump -i lo -Ss0 -n src 127.0.0.1 and dst 127.0.0.1 and port 5000

最後,使用 telnet 連線指定埠

$ date; telnet 127.0.0.1 5000; date

上面命令的輸出如下:

Tue Jan  3 16:39:05 CST 2017
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection timed out
Tue Jan  3 16:41:12 CST 2017

而從 tcpdump 命令的輸出可以看到:

16:39:05.690238 IP 127.0.0.1.58933 > 127.0.0.1.5000: Flags [S], seq 2286786481, win 43690, options [mss 65495,sackOK,TS val 179222486 ecr 0,nop,wscale 7], length 0
16:39:06.686988 IP 127.0.0.1.58933 > 127.0.0.1.5000: Flags [S], seq 2286786481, win 43690, options [mss 65495,sackOK,TS val 179222736 ecr 0,nop,wscale 7], length 0
16:39:08.690980 IP 127.0.0.1.58933 > 127.0.0.1.5000: Flags [S], seq 2286786481, win 43690, options [mss 65495,sackOK,TS val 179223237 ecr 0,nop,wscale 7], length 0
16:39:12.702973 IP 127.0.0.1.58933 > 127.0.0.1.5000: Flags [S], seq 2286786481, win 43690, options [mss 65495,sackOK,TS val 179224240 ecr 0,nop,wscale 7], length 0
16:39:20.718991 IP 127.0.0.1.58933 > 127.0.0.1.5000: Flags [S], seq 2286786481, win 43690, options [mss 65495,sackOK,TS val 179226244 ecr 0,nop,wscale 7], length 0
16:39:36.766986 IP 127.0.0.1.58933 > 127.0.0.1.5000: Flags [S], seq 2286786481, win 43690, options [mss 65495,sackOK,TS val 179230256 ecr 0,nop,wscale 7], length 0
16:40:08.830996 IP 127.0.0.1.58933 > 127.0.0.1.5000: Flags [S], seq 2286786481, win 43690, options [mss 65495,sackOK,TS val 179238272 ecr 0,nop,wscale 7], length 0

其中,Flags [S] 表示為 SYN 報文,可以看到總共傳送了 7 次 SYN 報文,最後一次的時間為 16:40:08,而 telnet 超時退出的時間為 16:41:12,相差 64 秒。

怎樣修改 connect timeout

對於很多客戶端程式來說,127 秒都是一個很長的時間,特別是對於區域網來說,公司內部往往都具有網路質量較好的區域網, 訪問內部的服務並不需要等待這麼長的超時,而可以 fail earlier。

Linux 核心中,net.ipv4.tcp_syn_retries 表示建立 TCP 連線時 SYN 報文重試的次數,預設為 6,可以通過 sysctl 命令檢視。

# sysctl -a | grep tcp_syn_retries
net.ipv4.tcp_syn_retries = 6

將其修改為 1,則可以將 connect 超時時間改為 3 秒,例如:

# sysctl net.ipv4.tcp_syn_retries=1

再次使用 telnet 驗證超時時間,如下:

$ date; telnet 127.0.0.1 5000; date
Fri Feb 17 09:50:12 CST 2017
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection timed out
Fri Feb 17 09:50:15 CST 2017

注意:sysctl 修改的核心引數在系統重啟後失效,如果需要持久化,可以修改系統配置檔案

例如:,對於 CentOS 7 來說,新增 net.ipv4.tcp_syn_retries = 1 到 /etc/sysctl.conf 中即可。

原文連結

http://www.chengweiyang.cn/2017/02/18/linux-connect-timeout/