1. 半連接隊列、全連接隊列基本概念
- 三次握手中,在第一步server收到client的syn后,把相關信息放到半連接隊列中,同時回復syn+ack給client(第二步),同時開啟一個定時器,如果超時還未收到 ACK 會進行 SYN+ACK 的重傳,重傳的次數由 tcp_synack_retries 值確定。在 CentOS 上這個值等于 5。;
- 第三步的時候server收到client的ack,如果這時全連接隊列沒滿,那么從半連接隊列拿出相關信息放入到全連接隊列中,否則按tcp_abort_on_overflow指示的執行。
1.1 tcp_abort_on_overflow
- tcp_abort_on_overflow 為 0 表示三次握手最后一步全連接隊列滿以后 server 會丟掉 client 發過來的 ACK,服務端隨后會進行重傳 SYN+ACK。
- tcp_abort_on_overflow 為 1 表示全連接隊列滿以后服務端直接發送 RST 給客戶端。
1.2 ss 命令
ss 命令可以查看全連接隊列的大小和當前等待 accept 的連接個數,執行 ss -lnt 即可
ss -lnt | grep :9090
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 51 50 *:9090 *:*
Recv-Q 表示 accept 隊列排隊的連接個數,Send-Q 表示全連接隊列(也就是 accept 隊列)的總大小。
2. 半連接隊列
2.1 半連接隊列的長度
半連接隊列的大小由3個值決定:
- 用戶層 listen 傳入的backlog
- 系統變量 net.ipv4.tcp_max_syn_backlog,默認值為 128
- 系統變量 net.core.somaxconn,默認值為 128
2.2 半連接隊列長度的計算過程
-
如果用戶傳入的 backlog 值大于系統變量 net.core.somaxconn 的值,用戶設置的 backlog 不會生效,使用系統變量值,默認為 128。
-
將上一步計算的backlog值穿給nr_table_entries,sysctl_max_syn_backlog 為 net.ipv4.tcp_max_syn_backlog 的值
int reqsk_queue_alloc(struct request_sock_queue *queue,unsigned int nr_table_entries)
{nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);nr_table_entries = max_t(u32, nr_table_entries, 8);nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);for (lopt->max_qlen_log = 3;(1 << lopt->max_qlen_log) < nr_table_entries;lopt->max_qlen_log++);
}
- 在 nr_table_entries 與 sysctl_max_syn_backlog 兩者中的較小值,賦值給 nr_table_entries(因為sysctl_max_syn_backlog默認是128,如果沒有修改,產生的最大值就只能是128)
- 在 nr_table_entries 和 8 取較大值,賦值給 nr_table_entries(最小只能是8)
- nr_table_entries + 1 向上取求最接近的最大 2 的指數次冪(保證是2的冪次)
- 通過 for 循環找不大于 nr_table_entries 最接近的 2 的對數值
3.全連接隊列
「全連接隊列」包含了服務端所有完成了三次握手,但是還未被應用調用 accept 取走的連接隊列。此時的 socket 處于 ESTABLISHED 狀態。每次應用調用 accept() 函數會移除隊列頭的連接。如果隊列為空,accept() 通常會阻塞。全連接隊列也被稱為 Accept 隊列。
3.1 全連接隊列的長度
全連接隊列的大小由3個值決定:
- 用戶層 listen 傳入的backlog
- 系統變量 net.core.somaxconn,默認值為 128
3.2 全連接隊列長度的計算過程
全連接隊列的大小是 listen 傳入的 backlog 和 somaxconn 中的較小值。
3.3 全連接隊列溢出的情況
-
如上圖,150166號包是三次握手中的第三步client發送ack給server,然后150167號包中client發送了一個長度為816的包給server。
-
因為在這個時候client認為連接建立成功(因為已經完成三次握手了),但是server上這個連接實際沒有ready(因為全連接隊列已經滿了),所以server直接丟掉client發來的ACK包,并且一段時間后,server認為自己第二次握手的syn+ack包丟包了,因此就重發syn+ack包(SYN+ACK重傳的次數是由操作系統的一個文件決定的/proc/sys/net/ipv4/tcp_synack_retries)
-
一段時間后client又收到了syn+ack包,認為第三次握手的ack丟包了,然后重傳這816個字節的ack包。一直到超時,client主動發fin包斷開該連接。