linux下connect超時時間探究
阿新 • • 發佈:2018-12-22
最近在linux做伺服器開發的時候,發現了一個現象:伺服器在啟動的時候呼叫了 connect 函式,因為連線了一個不可用的埠,導致connect最後報出了 “Connection timed out” 的錯誤。但是這中間過了六十多秒的時間。
為何會等待這麼長的時間才超時呢?這個時間又在哪裡設定?
《UNIX網路程式設計(第一卷)——套介面 API 和 X/Open 傳輸介面 API》一書的4.3節有寫到:
對於TCP套介面來說,函式 connect 激發TCP的三路握手過程,且僅在連結成功建立或出錯時才返回,返回的錯誤可能有如下幾種情況:
1. 如果TCP客戶沒有收到SYN分節的響應,則返回ETIMEDOUT。例如在4.4BSD中,當呼叫函式 connect 時,發出一個SYN,若無響應,等待6秒之後再發一個;若仍無響應,24秒鐘之後再發一個。若總共等待了75秒鐘之後仍未響應,則返回錯誤...
從書中可以看到 connect 建立TCP連結的過程中,會發送SYN包,如果沒有收到SYN包的回包,核心會多次傳送SYN包,並且每次重試的間隔會逐漸增加,避免傳送太多的SYN包影響網路。
在CentOS上,這個重試次數是可以設定的:
$ sysctl net.ipv4 | grep tcp net.ipv4.tcp_timestamps = 1 net.ipv4.tcp_window_scaling = 1 net.ipv4.tcp_sack = 1 net.ipv4.tcp_retrans_collapse = 1 net.ipv4.tcp_syn_retries = 5 net.ipv4.tcp_synack_retries= 5 net.ipv4.tcp_max_orphans = 262144 net.ipv4.tcp_max_tw_buckets = 262144 net.ipv4.tcp_keepalive_time = 7200 net.ipv4.tcp_keepalive_probes = 9 net.ipv4.tcp_keepalive_intvl = 75 net.ipv4.tcp_retries1 = 3 net.ipv4.tcp_retries2 = 15 net.ipv4.tcp_fin_timeout = 60 net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_tw_recycle= 0 net.ipv4.tcp_abort_on_overflow = 0 net.ipv4.tcp_stdurg = 0 net.ipv4.tcp_rfc1337 = 0 net.ipv4.tcp_max_syn_backlog = 2048 net.ipv4.tcp_orphan_retries = 0 net.ipv4.tcp_fack = 1 net.ipv4.tcp_reordering = 3 net.ipv4.tcp_ecn = 2 net.ipv4.tcp_dsack = 1 net.ipv4.tcp_mem = 639168 852224 1278336 net.ipv4.tcp_wmem = 4096 16384 4194304 net.ipv4.tcp_rmem = 4096 87380 4194304 net.ipv4.tcp_app_win = 31 net.ipv4.tcp_adv_win_scale = 2 net.ipv4.tcp_tw_reuse = 0 net.ipv4.tcp_frto = 2 net.ipv4.tcp_frto_response = 0 net.ipv4.tcp_low_latency = 0 net.ipv4.tcp_no_metrics_save = 0 net.ipv4.tcp_moderate_rcvbuf = 1 net.ipv4.tcp_tso_win_divisor = 3 net.ipv4.tcp_congestion_control = cubic net.ipv4.tcp_abc = 0 net.ipv4.tcp_mtu_probing = 0 net.ipv4.tcp_base_mss = 512 net.ipv4.tcp_workaround_signed_windows = 0 net.ipv4.tcp_challenge_ack_limit = 1000 net.ipv4.tcp_limit_output_bytes = 262144 net.ipv4.tcp_dma_copybreak = 4096 net.ipv4.tcp_slow_start_after_idle = 1 net.ipv4.tcp_available_congestion_control = cubic reno net.ipv4.tcp_allowed_congestion_control = cubic reno net.ipv4.tcp_max_ssthresh = 0 net.ipv4.tcp_thin_linear_timeouts = 0 net.ipv4.tcp_thin_dupack = 0 net.ipv4.tcp_min_tso_segs = 2 net.ipv4.tcp_invalid_ratelimit = 500
其中的 net.ipv4.tcp_syn_retries 選項控制著SYN的重試次數,可以通過如下命令來檢視和設定:
$ sysctl net.ipv4.tcp_syn_retries #檢視 net.ipv4.tcp_syn_retries = 5 $ sudo sysctl -w net.ipv4.tcp_syn_retries=1 #設定 net.ipv4.tcp_syn_retries = 1
下面用一個簡單的程式,來驗證各種次數下的connect超時時間:
#include <iostream> #include <sys/socket.h> #include <sys/time.h> #include <netinet/in.h> #include <errno.h> #include <string.h> #include <arpa/inet.h> long long GetCurrentMSecond() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * 1000 + tv.tv_usec / 1000; } int main() { int fd = 0; struct sockaddr_in addr; fd = socket(AF_INET, SOCK_STREAM, 0); socklen_t bufSize = 128 * 1024; int retCode = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bufSize, sizeof(bufSize)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("192.168.207.128"); addr.sin_port = htons(13500); //連線一個不用的埠,以保證會觸發超時 long long llBeginTime = GetCurrentMSecond(); if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { long long llEndTime = GetCurrentMSecond(); std::cout << "connect failed, errno: " << errno << ", error: " << strerror(errno) << ", cost time: " << llEndTime - llBeginTime << std::endl; return 0; } std::cout << "connect success" << std::endl; }
通過設定不同的重試次數,探究各種重試次數下的time out時間:
$ g++ connect.cpp -o main $ sudo sysctl -w net.ipv4.tcp_syn_retries=1 net.ipv4.tcp_syn_retries = 1 $ ./main connect failed, errno: 110, error: Connection timed out, cost time: 3000 $ sudo sysctl -w net.ipv4.tcp_syn_retries=2 net.ipv4.tcp_syn_retries = 2 $ ./main connect failed, errno: 110, error: Connection timed out, cost time: 7000
重試次數 | 超時時間(單位:毫秒) |
1 | 3000 |
2 | 7000 |
3 | 14999 |
4 | 31000 |
5 | 63001 |
6 | 126999 |
從表格中可以看到,當前設定重試次數為5的時候,超時時間是63秒,可以通過修改重試次數的方式,來改變connect的超時時間。