Linux中TCP三次握手與四次揮手介紹及調(diào)優(yōu)
TCP介紹
TCP是一種面向連接的單播協(xié)議,在發(fā)送數(shù)據(jù)前,通信雙方必須在彼此間建立一條連接。所謂的“連接”,其實(shí)是客戶端和服務(wù)器的內(nèi)存里保存的一份關(guān)于對(duì)方的信息,如ip地址、端口號(hào)等。
TCP可以看成是一種字節(jié)流,它會(huì)處理IP層或以下的層的丟包、重復(fù)以及錯(cuò)誤問(wèn)題。在連接的建立過(guò)程中,雙方需要交換一些連接的參數(shù)。這些參數(shù)可以放在TCP頭部。
TCP提供了一種可靠、面向連接、字節(jié)流、傳輸層的服務(wù),采用三次握手建立一個(gè)連接。采用4次揮手來(lái)關(guān)閉一個(gè)連接。
TCP三次握手
客戶端和服務(wù)端通信前要進(jìn)行連接,“3次握手”的作用就是雙方都能明確自己和對(duì)方的收、發(fā)能力是正常的。
- 第一次握手:客戶端發(fā)送網(wǎng)絡(luò)包,服務(wù)端收到了。這樣服務(wù)端就能得出結(jié)論:客戶端的發(fā)送能力、服務(wù)端的接收能力是正常的。
- 第二次握手:服務(wù)端發(fā)包,客戶端收到了。這樣客戶端就能得出結(jié)論:服務(wù)端的接收、發(fā)送能力,客戶端的接收、發(fā)送能力是正常的。從客戶端的視角來(lái)看,我接到了服務(wù)端發(fā)送過(guò)來(lái)的響應(yīng)數(shù)據(jù)包,說(shuō)明服務(wù)端接收到了我在第一次握手時(shí)發(fā)送的網(wǎng)絡(luò)包,并且成功發(fā)送了響應(yīng)數(shù)據(jù)包,這就說(shuō)明,服務(wù)端的接收、發(fā)送能力正常。而另一方面,我收到了服務(wù)端的響應(yīng)數(shù)據(jù)包,說(shuō)明我第一次發(fā)送的網(wǎng)絡(luò)包成功到達(dá)服務(wù)端,這樣,我自己的發(fā)送和接收能力也是正常的。
- 第三次握手:客戶端發(fā)包,服務(wù)端收到了。這樣服務(wù)端就能得出結(jié)論:客戶端的接收、發(fā)送能力,服務(wù)端的發(fā)送、接收能力是正常的。第一、二次握手后,服務(wù)端并不知道客戶端的接收能力以及自己的發(fā)送能力是否正常。而在第三次握手時(shí),服務(wù)端收到了客戶端對(duì)第二次握手作的回應(yīng)。從服務(wù)端的角度,我在第二次握手時(shí)的響應(yīng)數(shù)據(jù)發(fā)送出去了,客戶端接收到了。所以,我的發(fā)送能力是正常的。而客戶端的接收能力也是正常的。
經(jīng)歷了上面的三次握手過(guò)程,客戶端和服務(wù)端都確認(rèn)了自己的接收、發(fā)送能力是正常的。之后就可以正常通信了。
TCP三次握手中的狀態(tài)
在 TCP 三次握手的時(shí)候,Linux 內(nèi)核會(huì)維護(hù)兩個(gè)隊(duì)列,分別是:
- 半連接隊(duì)列,也稱 SYN 隊(duì)列;
- 全連接隊(duì)列,也稱 accepet 隊(duì)列;
服務(wù)端收到客戶端發(fā)起的 SYN 請(qǐng)求后,內(nèi)核會(huì)把該連接存儲(chǔ)到半連接隊(duì)列,并向客戶端響應(yīng) SYN+ACK,接著客戶端會(huì)返回 ACK,服務(wù)端收到第三次握手的 ACK 后,內(nèi)核會(huì)把連接從半連接隊(duì)列移除,然后創(chuàng)建新的完全的連接,并將其添加到 accept 隊(duì)列,等待進(jìn)程調(diào)用 accept 函數(shù)時(shí)把連接取出來(lái)。
不管是半連接隊(duì)列還是全連接隊(duì)列,都有最大長(zhǎng)度限制,超過(guò)限制時(shí),內(nèi)核會(huì)直接丟棄,或返回 RST 包。
半連接
很遺憾,我們沒有命令可以查看系統(tǒng)的半連接隊(duì)列數(shù)量。但是我們可以抓住 TCP 半連接的特點(diǎn),就是服務(wù)端處于 SYN_RECV 狀態(tài)的 TCP 連接,就是 TCP 半連接隊(duì)列。使用如下命令計(jì)算當(dāng)前 TCP 半連接隊(duì)列長(zhǎng)度:
- $ netstat |grep SYN_RECV |wc -l
- 1723
SYN_RECV 狀態(tài)下,服務(wù)器必須建立一個(gè) SYN 半連接隊(duì)列來(lái)維護(hù)未完成的握手信息,當(dāng)這個(gè)隊(duì)列溢出后,服務(wù)器將無(wú)法再建立新連接。
如何模擬 TCP 半連接隊(duì)列溢出場(chǎng)景?
模擬 TCP 半連接溢出場(chǎng)景不難,實(shí)際上就是對(duì)服務(wù)端一直發(fā)送 TCP SYN 包,但是不回第三次握手 ACK,這樣就會(huì)使得服務(wù)端有大量的處于 SYN_RECV 狀態(tài)的 TCP 連接。這其實(shí)也就是所謂的 SYN 洪泛、SYN 攻擊、DDos 攻擊。

實(shí)驗(yàn)環(huán)境:
- 客戶端和服務(wù)端都是 CentOS Linux release 7.9.2009 (Core) ,Linux 內(nèi)核版本 3.10.0-1160.15.2.el7.x86_64
- 服務(wù)端 IP 172.16.0.20,客戶端 IP 172.16.0.157
- 服務(wù)端是 Nginx 服務(wù),端口為 80
本次實(shí)驗(yàn)使用 hping3 工具模擬 SYN 攻擊:
- $ hping3 -S -p 80 --flood 172.16.0.20
- HPING 172.16.0.20 (eth0 172.16.0.20): S set, 40 headers + 0 data bytes
- hping in flood mode, no replies will be shown
新連接建立失敗的原因有很多,怎樣獲得由于隊(duì)列已滿而引發(fā)的失敗次數(shù)呢?netstat -s 命令給出的統(tǒng)計(jì)結(jié)果中可以得到。
- $ netstat -s|grep "SYNs to LISTEN"
- 1541918 SYNs to LISTEN sockets dropped
這里給出的是隊(duì)列溢出導(dǎo)致 SYN 被丟棄的個(gè)數(shù)。注意這是一個(gè)累計(jì)值,如果數(shù)值在持續(xù)增加,則應(yīng)該調(diào)大 SYN 半連接隊(duì)列。修改隊(duì)列大小的方法,是設(shè)置 Linux 的 tcp_max_syn_backlog 參數(shù):
- sysctl -w net.ipv4.tcp_max_syn_backlog = 1024
如果 SYN 半連接隊(duì)列已滿,只能丟棄連接嗎?并不是這樣,開啟 syncookies 功能就可以在不使用 SYN 隊(duì)列的情況下成功建立連接。syncookies 是這么做的:服務(wù)器根據(jù)當(dāng)前狀態(tài)計(jì)算出一個(gè)值,放在己方發(fā)出的 SYN+ACK 報(bào)文中發(fā)出,當(dāng)客戶端返回 ACK 報(bào)文時(shí),取出該值驗(yàn)證,如果合法,就認(rèn)為連接建立成功,如下圖所示。
Linux 下怎樣開啟 syncookies 功能呢?修改 tcp_syncookies 參數(shù)即可,其中值為 0 時(shí)表示關(guān)閉該功能,2 表示無(wú)條件開啟功能,而 1 則表示僅當(dāng) SYN 半連接隊(duì)列放不下時(shí),再啟用它。由于 syncookie 僅用于應(yīng)對(duì) SYN 泛洪攻擊(攻擊者惡意構(gòu)造大量的 SYN 報(bào)文發(fā)送給服務(wù)器,造成 SYN 半連接隊(duì)列溢出,導(dǎo)致正常客戶端的連接無(wú)法建立),這種方式建立的連接,許多 TCP 特性都無(wú)法使用。所以,應(yīng)當(dāng)把 tcp_syncookies 設(shè)置為 1,僅在隊(duì)列滿時(shí)再啟用。
- sysctl -w net.ipv4.tcp_syncookies = 1
全連接
在服務(wù)端可以使用 ss 命令,來(lái)查看 TCP 全連接隊(duì)列的情況:但需要注意的是 ss 命令獲取的 Recv-Q/Send-Q 在「LISTEN 狀態(tài)」和「非 LISTEN 狀態(tài)」所表達(dá)的含義是不同的。從下面的內(nèi)核代碼可以看出區(qū)別:
- static void tcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r,
- void *_info)
- {
- const struct tcp_sock *tp = tcp_sk(sk);
- struct tcp_info *info = _info;
- if (sk->sk_state == TCP_LISTEN) {
- r->idiag_rqueue = sk->sk_ack_backlog;
- r->idiag_wqueue = sk->sk_max_ack_backlog;
- } else {
- r->idiag_rqueue = tp->rcv_nxt - tp->copied_seq;
- r->idiag_wqueue = tp->write_seq - tp->snd_una;
- }
- if (info != NULL)
- tcp_get_info(sk, info);
- }
在「LISTEN 狀態(tài)」時(shí),Recv-Q/Send-Q 表示的含義如下:
- $ ss -ltnp
- LISTEN 0 1024 *:8081 *:* users:(("java",pid=5686,fd=310))
- Recv-Q:當(dāng)前全連接隊(duì)列的大小,也就是當(dāng)前已完成三次握手并等待服務(wù)端 accept() 的 TCP 連接;
- Send-Q:當(dāng)前全連接最大隊(duì)列長(zhǎng)度,上面的輸出結(jié)果說(shuō)明監(jiān)聽 8088 端口的 TCP 服務(wù),最大全連接長(zhǎng)度為 1024;
在「非 LISTEN 狀態(tài)」時(shí),Recv-Q/Send-Q 表示的含義如下:
- $ ss -tnp
- ESTAB 0 0 172.16.0.20:57672 172.16.0.20:2181 users:(("java",pid=5686,fd=292))
- Recv-Q:已收到但未被應(yīng)用進(jìn)程讀取的字節(jié)數(shù);
- Send-Q:已發(fā)送但未收到確認(rèn)的字節(jié)數(shù);
如何模擬 TCP 全連接隊(duì)列溢出的場(chǎng)景?
實(shí)驗(yàn)環(huán)境:
- 客戶端和服務(wù)端都是 CentOS Linux release 7.9.2009 (Core) ,Linux 內(nèi)核版本 3.10.0-1160.15.2.el7.x86_64
- 服務(wù)端 IP 172.16.0.20,客戶端 IP 172.16.0.157
- 服務(wù)端是 Nginx 服務(wù),端口為 80
ab是apache bench命令的縮寫。ab是apache自帶的壓力測(cè)試工具。ab非常實(shí)用,它不僅可以對(duì)apache服務(wù)器進(jìn)行網(wǎng)站訪問(wèn)壓力測(cè)試,也可以對(duì)或其它類型的服務(wù)器進(jìn)行壓力測(cè)試。比如nginx、tomcat、IIS等。ab的原理:ab命令會(huì)創(chuàng)建多個(gè)并發(fā)訪問(wèn)線程,模擬多個(gè)訪問(wèn)者同時(shí)對(duì)某一URL地址進(jìn)行訪問(wèn)。它的測(cè)試目標(biāo)是基于URL的,因此,它既可以用來(lái)測(cè)試apache的負(fù)載壓力,也可以測(cè)試nginx、lighthttp、tomcat、IIS等其它Web服務(wù)器的壓力。ab命令對(duì)發(fā)出負(fù)載的計(jì)算機(jī)要求很低,它既不會(huì)占用很高CPU,也不會(huì)占用很多內(nèi)存。但卻會(huì)給目標(biāo)服務(wù)器造成巨大的負(fù)載,其原理類似CC攻擊。自己測(cè)試使用也需要注意,否則一次上太多的負(fù)載。可能造成目標(biāo)服務(wù)器資源耗完,嚴(yán)重時(shí)甚至導(dǎo)致死機(jī)。
TCP 全連接隊(duì)列的最大值取決于 somaxconn 和 backlog 之間的最小值,也就是 min(somaxconn, backlog)。從下面的 Linux 內(nèi)核代碼可以得知:
- int __sys_listen(int fd, int backlog)
- {
- struct socket *sock;
- int err, fput_needed;
- int somaxconn;
- sock = sockfd_lookup_light(fd, &err, &fput_needed);
- if (sock) {
- somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
- if ((unsigned int)backlog > somaxconn)
- backlog = somaxconn;
- err = security_socket_listen(sock, backlog);
- if (!err)
- err = sock->ops->listen(sock, backlog);
- fput_light(sock->file, fput_needed);
- }
- return err;
- }
- somaxconn 是 Linux 內(nèi)核的參數(shù),默認(rèn)值是 128,可以通過(guò) /proc/sys/net/core/somaxconn 來(lái)設(shè)置其值;我們?cè)O(shè)置為40000了。
- backlog 是 listen(int sockfd, int backlog) 函數(shù)中的 backlog 大小,Nginx 默認(rèn)值是 511,可以通過(guò)修改配置文件設(shè)置其長(zhǎng)度;
所以測(cè)試環(huán)境的 TCP 全連接隊(duì)列最大值為 min(128, 511),也就是 511,可以執(zhí)行 ss 命令查看:
- ss -tulnp|grep 80
- tcp LISTEN 0 511 *:80 *:* users:(("nginx",pid=22913,fd=6),("nginx",pid=22912,fd=6),("nginx",pid=22911,fd=6))
- tcp LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=22913,fd=7),("nginx",pid=22912,fd=7),("nginx",pid=22911,fd=7))
客戶端執(zhí)行 ab 命令對(duì)服務(wù)端發(fā)起壓力測(cè)試,并發(fā) 1 萬(wàn)個(gè)連接,發(fā)送10萬(wàn)個(gè)包:
- -n表示總的請(qǐng)求數(shù)為10000
- -c表示并發(fā)請(qǐng)求數(shù)為1000
- ab -c 10000 -n 100000 http://172.16.0.20:80/
- This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
- Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
- Licensed to The Apache Software Foundation, http://www.apache.org/
- Benchmarking 172.16.0.20 (be patient)
- Completed 10000 requests
- Completed 20000 requests
- Completed 30000 requests
- Completed 40000 requests
- Completed 50000 requests
- Completed 60000 requests
- Completed 70000 requests
- Completed 80000 requests
- Completed 90000 requests
- Completed 100000 requests
- Finished 100000 requests
- Server Software: nginx/1.20.1
- Server Hostname: 172.16.0.20
- Server Port: 80
- Document Path: /
- Document Length: 4833 bytes
- Concurrency Level: 10000
- Time taken for tests: 2.698 seconds
- Complete requests: 100000
- Failed requests: 167336
- (Connect: 0, Receive: 0, Length: 84384, Exceptions: 82952)
- Write errors: 0
- Total transferred: 86399264 bytes
- HTML transferred: 82392984 bytes
- Requests per second: 37069.19 [#/sec] (mean)
- Time per request: 269.766 [ms] (mean)
- Time per request: 0.027 [ms] (mean, across all concurrent requests)
- Transfer rate: 31276.86 [Kbytes/sec] received
- Connection Times (ms)
- min mean[+/-sd] median max
- Connect: 0 129 151.5 106 1144
- Processing: 39 121 37.7 114 239
- Waiting: 0 23 51.8 0 159
- Total: 142 250 152.4 224 1346
- Percentage of the requests served within a certain time (ms)
- 50% 224
- 66% 227
- 75% 232
- 80% 236
- 90% 283
- 95% 299
- 98% 1216
- 99% 1228
- 100% 1346 (longest request)
其間共執(zhí)行了兩次 ss 命令,從上面的輸出結(jié)果,可以發(fā)現(xiàn)當(dāng)前 TCP 全連接隊(duì)列上升到了 512 大小,超過(guò)了最大 TCP 全連接隊(duì)列。
- ss -tulnp|grep 80
- tcp LISTEN 411 511 *:80 *:* users:(("nginx",pid=22913,fd=6),("nginx",pid=22912,fd=6),("nginx",pid=22911,fd=6))
- ss -tulnp|grep 80
- tcp LISTEN 512 511 *:80 *:* users:(("nginx",pid=22913,fd=6),("nginx",pid=22912,fd=6),("nginx",pid=22911,fd=6))
當(dāng)超過(guò)了 TCP 最大全連接隊(duì)列,服務(wù)端則會(huì)丟掉后續(xù)進(jìn)來(lái)的 TCP 連接,丟掉的 TCP 連接的個(gè)數(shù)會(huì)被統(tǒng)計(jì)起來(lái),我們可以使用 netstat -s 命令來(lái)查看:
- netstat -s|grep overflowed
- 1233972 times the listen queue of a socket overflowed
上面看到的 1233972 times ,表示全連接隊(duì)列溢出的次數(shù),注意這個(gè)是累計(jì)值。可以隔幾秒鐘執(zhí)行下,如果這個(gè)數(shù)字一直在增加的話肯定全連接隊(duì)列滿了。
- netstat -s|grep overflowed
- 1292022 times the listen queue of a socket overflowed
從上面的模擬結(jié)果,可以得知,當(dāng)服務(wù)端并發(fā)處理大量請(qǐng)求時(shí),如果 TCP 全連接隊(duì)列過(guò)小,就容易溢出。發(fā)生 TCP 全連接隊(duì)列溢出的時(shí)候,后續(xù)的請(qǐng)求就會(huì)被丟棄,這樣就會(huì)出現(xiàn)服務(wù)端請(qǐng)求數(shù)量上不去的現(xiàn)象。
Linux 有個(gè)參數(shù)可以指定當(dāng) TCP 全連接隊(duì)列滿了會(huì)使用什么策略來(lái)回應(yīng)客戶端
實(shí)際上,丟棄連接只是 Linux 的默認(rèn)行為,我們還可以選擇向客戶端發(fā)送 RST 復(fù)位報(bào)文,告訴客戶端連接已經(jīng)建立失敗。
- $ cat /proc/sys/net/ipv4/tcp_abort_on_overflow
- 0
tcp_abort_on_overflow 共有兩個(gè)值分別是 0 和 1,其分別表示:
- 0 :如果全連接隊(duì)列滿了,那么 server 扔掉 client 發(fā)過(guò)來(lái)的 ack ;
- 1 :如果全連接隊(duì)列滿了,server 發(fā)送一個(gè) reset 包給 client,表示廢掉這個(gè)握手過(guò)程和這個(gè)連接;
如果要想知道客戶端連接不上服務(wù)端,是不是服務(wù)端 TCP 全連接隊(duì)列滿的原因,那么可以把 tcp_abort_on_overflow 設(shè)置為 1,這時(shí)如果在客戶端異常中可以看到很多 connection reset by peer 的錯(cuò)誤,那么就可以證明是由于服務(wù)端 TCP 全連接隊(duì)列溢出的問(wèn)題。通常情況下,應(yīng)當(dāng)把 tcp_abort_on_overflow 設(shè)置為 0,因?yàn)檫@樣更有利于應(yīng)對(duì)突發(fā)流量。所以,tcp_abort_on_overflow 設(shè)為 0 可以提高連接建立的成功率,只有你非常肯定 TCP 全連接隊(duì)列會(huì)長(zhǎng)期溢出時(shí),才能設(shè)置為 1 以盡快通知客戶端。
- sysctl -w net.ipv4.tcp_abort_on_overflow = 0
如何增大 TCP 全連接隊(duì)列呢?
根據(jù)上面提到的TCP 全連接隊(duì)列的最大值取決于 somaxconn 和 backlog 之間的最小值,也就是 min(somaxconn, backlog)。我們現(xiàn)在調(diào)整somaxconn值:
- $ sysctl -w net.core.somaxconn=65535
調(diào)整nginx配置:
- server {
- listen 80 backlog=65535;
- ...
最后要重啟 Nginx 服務(wù),因?yàn)橹挥兄匦抡{(diào)用 listen() 函數(shù) TCP 全連接隊(duì)列才會(huì)重新初始化。服務(wù)端執(zhí)行 ss 命令,查看 TCP 全連接隊(duì)列大小:
- $ ss -tulntp|grep 80
- tcp LISTEN 0 65535 *:80 *:* users:(("nginx",pid=24212,fd=6),("nginx",pid=24211,fd=6),("nginx",pid=24210,fd=6))
從執(zhí)行結(jié)果,可以發(fā)現(xiàn) TCP 全連接最大值為 65535。
增大 TCP 全連接隊(duì)列后,繼續(xù)壓測(cè)
- ab -c 10000 -n 100000 http://172.16.0.20:80/
- This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
- Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
- Licensed to The Apache Software Foundation, http://www.apache.org/
- Benchmarking 172.16.0.20 (be patient)
- Completed 10000 requests
- Completed 20000 requests
- Completed 30000 requests
- Completed 40000 requests
- Completed 50000 requests
- Completed 60000 requests
- Completed 70000 requests
- Completed 80000 requests
- Completed 90000 requests
- Completed 100000 requests
- Finished 100000 requests
- Server Software: nginx/1.20.1
- Server Hostname: 172.16.0.20
- Server Port: 80
- Document Path: /
- Document Length: 4833 bytes
- Concurrency Level: 10000
- Time taken for tests: 2.844 seconds
- Complete requests: 100000
- Failed requests: 178364
- (Connect: 0, Receive: 0, Length: 89728, Exceptions: 88636)
- Write errors: 0
- Total transferred: 57592752 bytes
- HTML transferred: 54922212 bytes
- Requests per second: 35159.35 [#/sec] (mean)
- Time per request: 284.419 [ms] (mean)
- Time per request: 0.028 [ms] (mean, across all concurrent requests)
- Transfer rate: 19774.64 [Kbytes/sec] received
- Connection Times (ms)
- min mean[+/-sd] median max
- Connect: 0 130 18.3 130 172
- Processing: 45 142 40.1 138 281
- Waiting: 0 19 52.4 0 185
- Total: 159 272 31.2 272 390
- Percentage of the requests served within a certain time (ms)
- 50% 272
- 66% 274
- 75% 275
- 80% 276
- 90% 280
- 95% 358
- 98% 370
- 99% 375
- 100% 390 (longest request)
服務(wù)端執(zhí)行 ss 命令,查看 TCP 全連接隊(duì)列使用情況:
- $ ss -tulnp|grep 80
- tcp LISTEN 8 65535 *:80 *:* users:(("nginx",pid=24212,fd=6),("nginx",pid=24211,fd=6),("nginx",pid=24210,fd=6))
- $ ss -tulnp|grep 80
- tcp LISTEN 352 65535 *:80 *:* users:(("nginx",pid=24212,fd=6),("nginx",pid=24211,fd=6),("nginx",pid=24210,fd=6))
- $ ss -tulnp|grep 80
- tcp LISTEN 0 65535 *:80 *:* users:(("nginx",pid=24212,fd=6),("nginx",pid=24211,fd=6),("nginx",pid=24210,fd=6))
從上面的執(zhí)行結(jié)果,可以發(fā)現(xiàn)全連接隊(duì)列使用增長(zhǎng)的很快,但是一直都沒有超過(guò)最大值,所以就不會(huì)溢出,那么 netstat -s 就不會(huì)有 TCP 全連接隊(duì)列溢出個(gè)數(shù)繼續(xù)增加:
- $ netstat -s|grep overflowed
- 1540879 times the listen queue of a socket overflowed
- $ netstat -s|grep overflowed
- 1540879 times the listen queue of a socket overflowed
- $ netstat -s|grep overflowed
- 1540879 times the listen queue of a socket overflowed
- $ netstat -s|grep overflowed
- 1540879 times the listen queue of a socket overflowed
說(shuō)明 TCP 全連接隊(duì)列最大值從 512 增大到 65535 后,服務(wù)端抗住了 10 萬(wàn)連接并發(fā)請(qǐng)求,也沒有發(fā)生全連接隊(duì)列溢出的現(xiàn)象了。如果持續(xù)不斷地有連接因?yàn)?TCP 全連接隊(duì)列溢出被丟棄,就應(yīng)該調(diào)大 backlog 以及 somaxconn 參數(shù)。
TCP四次揮手
- 第一次揮手:主動(dòng)關(guān)閉方發(fā)送一個(gè)FIN,用來(lái)關(guān)閉主動(dòng)方到被動(dòng)關(guān)閉方的數(shù)據(jù)傳送,也就是主動(dòng)關(guān)閉方告訴被動(dòng)關(guān)閉方:我已經(jīng)不會(huì)再給你發(fā)數(shù)據(jù)了(當(dāng)然,在fin包之前發(fā)送出去的數(shù)據(jù),如果沒有收到對(duì)應(yīng)的ack確認(rèn)報(bào)文,主動(dòng)關(guān)閉方依然會(huì)重發(fā)這些數(shù)據(jù)),但此時(shí)主動(dòng)關(guān)閉方還可以接受數(shù)據(jù)。
- 第二次揮手:被動(dòng)關(guān)閉方收到FIN包后,發(fā)送一個(gè)ACK給對(duì)方,確認(rèn)序號(hào)為收到序號(hào)+1(與SYN相同,一個(gè)FIN占用一個(gè)序號(hào))。
- 第三次揮手:被動(dòng)關(guān)閉方發(fā)送一個(gè)FIN,用來(lái)關(guān)閉被動(dòng)關(guān)閉方到主動(dòng)關(guān)閉方的數(shù)據(jù)傳送,也就是告訴主動(dòng)關(guān)閉方,我的數(shù)據(jù)也發(fā)送完了,不會(huì)再給你發(fā)數(shù)據(jù)了。
- 第四次揮手:主動(dòng)關(guān)閉方收到FIN后,發(fā)送一個(gè)ACK給被動(dòng)關(guān)閉方,確認(rèn)序號(hào)為收到序號(hào)+1,至此,完成四次揮手。
互聯(lián)網(wǎng)中往往服務(wù)器才是主動(dòng)關(guān)閉連接的一方。這是因?yàn)椋琀TTP 消息是單向傳輸協(xié)議,服務(wù)器接收完請(qǐng)求才能生成響應(yīng),發(fā)送完響應(yīng)后就會(huì)立刻關(guān)閉 TCP 連接,這樣及時(shí)釋放了資源,能夠?yàn)楦嗟挠脩舴?wù)。
四次揮手的狀態(tài)
我們來(lái)看斷開連接的時(shí)候的狀態(tài)時(shí)序圖:
其實(shí)四次揮手只涉及兩種報(bào)文:FIN 和 ACK。FIN 就是 Finish 結(jié)束連接的意思,誰(shuí)發(fā)出 FIN 報(bào)文,就表示它將不再發(fā)送任何數(shù)據(jù),關(guān)閉這一方向的傳輸通道。ACK 是 Acknowledge 確認(rèn)的意思,它用來(lái)通知對(duì)方,你方的發(fā)送通道已經(jīng)關(guān)閉。
- 當(dāng)主動(dòng)方關(guān)閉連接時(shí),會(huì)發(fā)送 FIN 報(bào)文,此時(shí)主動(dòng)方的連接狀態(tài)由 ESTABLISHED 變?yōu)?FIN_WAIT1。
- 當(dāng)被動(dòng)方收到 FIN 報(bào)文后,內(nèi)核自動(dòng)回復(fù) ACK 報(bào)文,連接狀態(tài)由 ESTABLISHED 變?yōu)?CLOSE_WAIT,顧名思義,它在等待進(jìn)程調(diào)用 close 函數(shù)關(guān)閉連接。
- 當(dāng)主動(dòng)方接收到這個(gè) ACK 報(bào)文后,連接狀態(tài)由 FIN_WAIT1 變?yōu)?FIN_WAIT2,主動(dòng)方的發(fā)送通道就關(guān)閉了。
- 當(dāng)被動(dòng)方進(jìn)入 CLOSE_WAIT 狀態(tài)時(shí),進(jìn)程的 read 函數(shù)會(huì)返回 0,這樣開發(fā)人員就會(huì)有針對(duì)性地調(diào)用 close 函數(shù),進(jìn)而觸發(fā)內(nèi)核發(fā)送 FIN 報(bào)文,此時(shí)被動(dòng)方連接的狀態(tài)變?yōu)?LAST_ACK。當(dāng)主動(dòng)方收到這個(gè) FIN 報(bào)文時(shí),內(nèi)核會(huì)自動(dòng)回復(fù) ACK,同時(shí)連接的狀態(tài)由 FIN_WAIT2 變?yōu)? TIME_WAIT,Linux 系統(tǒng)下等待的時(shí)間設(shè)為 2MSL,MSL 是 Maximum Segment Lifetime,報(bào)文最大生存時(shí)間,它是任何報(bào)文在網(wǎng)絡(luò)上存在的最長(zhǎng)時(shí)間,超過(guò)這個(gè)時(shí)間報(bào)文將被丟棄。TIME_WAIT 狀態(tài)的連接才會(huì)徹底關(guān)閉。
當(dāng)被動(dòng)方收到 ACK 報(bào)文后,連接就會(huì)關(guān)閉。
主動(dòng)方TIME_WAIT優(yōu)化
大量處于 TIME_WAIT 狀態(tài)的連接,它們會(huì)占用大量?jī)?nèi)存和端口資源。這時(shí),我們可以優(yōu)化與 TIME_WAIT 狀態(tài)相關(guān)的內(nèi)核選項(xiàng),比如采取下面幾種措施。
- 增大處于 TIME_WAIT 狀態(tài)的連接數(shù)量 net.ipv4.tcp_max_tw_buckets ,并增大連接跟蹤表的大小 net.netfilter.nf_conntrack_max。
- sysctl -w net.ipv4.tcp_max_tw_buckets=1048576
- sysctl -w net.netfilter.nf_conntrack_max=1048576
- 減小FIN_WAIT2狀態(tài)的參數(shù) net.ipv4.tcp_fin_timeout 的時(shí)間和減小TIME_WAIT 狀態(tài)的參數(shù)net.netfilter.nf_conntrack_tcp_timeout_time_wait的時(shí)間 ,讓系統(tǒng)盡快釋放它們所占用的資源。
- sysctl -w net.ipv4.tcp_fin_timeout=15
- sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30
- 開啟端口復(fù)用 net.ipv4.tcp_tw_reuse。這樣,被 TIME_WAIT 狀態(tài)占用的端口,還能用到新建的連接中。
- sysctl -w net.ipv4.tcp_tw_reuse=1
- 增大本地端口的范圍 net.ipv4.ip_local_port_range 。這樣就可以支持更多連接,提高整體的并發(fā)能力。
- sysctl -w net.ipv4.ip_local_port_range="1024 65535"
- 增加最大文件描述符的數(shù)量。你可以使用 fs.nr_open 和 fs.file-max ,分別增大進(jìn)程和系統(tǒng)的最大文件描述符數(shù);或在應(yīng)用程序的 systemd 配置文件中,配置 LimitNOFILE ,設(shè)置應(yīng)用程序的最大文件描述符數(shù)。
- sysctl -w fs.nr_open=1048576
- sysctl -w fs.file-max=1048576
巨人的肩膀
[1] 系統(tǒng)性能調(diào)優(yōu)必知必會(huì).陶輝.極客時(shí)間.
[2] TCP/IP詳解卷二:實(shí)現(xiàn)
本文轉(zhuǎn)載自微信公眾號(hào)「 運(yùn)維開發(fā)故事」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 運(yùn)維開發(fā)故事公眾號(hào)。
【編輯推薦】