在談time_wait狀態(tài)前廊驼,我們來看下Tcp的四次揮手吧:
簡單解釋一下這張網(wǎng)圖吧:
- 客戶端傳完數(shù)據(jù)据过,想拔*無情,此時就會發(fā)送TCP首部
FIN
標志位置為1
的報文妒挎,跟服務(wù)端說我完事了绳锅。之后客戶端進入了FIN_WAIT_1
狀態(tài)。 - 此時服務(wù)端收到報文后酝掩,發(fā)送確認報文
ACK
,表示老娘知道你一滴都沒有了鳞芙,那我也就準備收拾收拾結(jié)束了。進入了CLOSE_WAIT
狀態(tài)期虾。 表示等著進程調(diào)用close 函數(shù)關(guān)閉連接原朝。 - 客戶端收到了
ACK
之后,狀態(tài)就從FIN_WAIT1
變成了FIN_WAIT2
〕瓜現(xiàn)在客戶端的發(fā)送通道就關(guān)了竿拆。但是此時還可以接收數(shù)據(jù)。 - 當服務(wù)端進入
CLOSE_WAIT
之后宾尚,他還會繼續(xù)處理數(shù)據(jù)丙笋,等到等到進程的 read 函數(shù)返回 0 后,就會調(diào)用closLAST_ACKe
函數(shù)煌贴,開始發(fā)送FIN
報文御板,進入LAST_ACK
狀態(tài) - 客戶端收到FIN報文后,會回復(fù)
ACK
報文給服務(wù)端牛郑,然后終于到了TIME_WAIT
狀態(tài)(等死我了)怠肋。在 Linux 系統(tǒng)下大約等待 1 分鐘后,TIME_WAIT 狀態(tài)的連接才會徹底關(guān)閉淹朋。 - 當服務(wù)端收到最后的ACK之后笙各,兩邊就關(guān)啦。(光燈睡覺)
這時候你會發(fā)現(xiàn)一件事情:主動關(guān)閉連接的础芍,才有 TIME_WAIT 狀態(tài)杈抢。
是的,沒錯仑性,你真棒惶楼!
為什么要有TIME_WAIT呢?
TIME_WAIT
是主動方四次揮手的最后一個狀態(tài),也是最常遇見的狀態(tài)歼捐。
- 保證連接正確關(guān)閉
TIME-WAIT 一個作用是等待足夠的時間以確保最后的 ACK 能讓被動關(guān)閉方接收何陆,從而幫助其正常關(guān)閉。
明明客戶端發(fā)完最后的ACK了豹储,我客戶端憑啥還要等按ぁ?哦吼剥扣,這不就暴露問題了嗎晃洒?客戶端發(fā)了ACK,服務(wù)端沒收到怎么辦朦乏?那就再發(fā)個FIN給客戶端球及。這時候就會重傳tcp_orphan_retries 次,這時候沒有TIME_WAIT豈不是很尬呻疹?你客戶端連接收通道都關(guān)了我服務(wù)端傳給誰俺砸?刽锤?镊尺?那服務(wù)端還關(guān)不關(guān)了?就一直卡在了LASTACK
狀態(tài)了并思。
- 防止舊連接的數(shù)據(jù)包
TIME-WAIT 還有一個作用是防止收到歷史數(shù)據(jù)庐氮,從而導致數(shù)據(jù)錯亂的問題。
試想一個場景沒有 TIME_WAIT
我直接關(guān)了宋彼,在關(guān)閉之前服務(wù)端一個報文因為網(wǎng)絡(luò)原因延遲了弄砍,到達的時候這個接口被重新握手啟動了。
新的客戶端收到了延遲的數(shù)據(jù)包输涕,那就尷尬了音婶。
所以,TCP 就設(shè)計出了這么一個機制莱坎,經(jīng)過 2MSL 這個時間衣式,足以讓兩個方向上的數(shù)據(jù)包都被丟棄,
使得原來連接的數(shù)據(jù)包在網(wǎng)絡(luò)中都自然消失檐什,再出現(xiàn)的數(shù)據(jù)包一定都是新建立連接所產(chǎn)生的碴卧。
為什么TIME_WAIT是2MSL呢?
之前有提到一手:在 Linux 系統(tǒng)下大約等待 1 分鐘后,TIME_WAIT 狀態(tài)的連接才會徹底關(guān)閉乃正。
那么為什么 TIME_WAIT 狀態(tài)要保持 60 秒呢住册?
因為FIN_WAIT2,TIME_WAIT狀態(tài)都需要保持 2MSL 時長烫葬。MSL 全稱是 Maximum
Segment Lifetime界弧,它定義了一個報文在網(wǎng)絡(luò)中的最長生存時間(報文每經(jīng)過一次路由器的轉(zhuǎn)發(fā),IP頭部的 TTL 字段就會減 1搭综,減到 0 時報文就被丟棄垢箕,這就限制了報文的最長存活時間)。
為什么是 2 MSL 的時長呢兑巾?這其實是相當于至少允許報文丟失一次条获。比如,若 ACK 在一個 MSL 內(nèi)丟失蒋歌,這樣被動方重發(fā)的 FIN 會在第 2 個 MSL 內(nèi)到達帅掘,TIME_WAIT 狀態(tài)的連接可以應(yīng)對。
為什么不是更長的時間呢堂油?丟包率1%算是糟糕了吧修档,連續(xù)丟兩次呢?我靠府框,1/1W的概率吱窝,還是丟了吧。
因此迫靖,TIME_WAIT 和 FIN_WAIT2 狀態(tài)的最大時長都是 2 MSL院峡,由于在 Linux 系統(tǒng)中,MSL 的值固定為 30 秒系宜,所以它們都是 60 秒照激。
那要怎么優(yōu)化TIME_WAIT狀態(tài)呢?
為什么要優(yōu)化TIME_WAIT盹牧?
之前說到了TIME_WAIT的兩個作用俩垃,還挺得勁,那為什么要優(yōu)化它呢汰寓?(不優(yōu)化他你怎么面試呢吆寨?)
話不是這么說的,你看哈雖然 TIME_WAIT 狀態(tài)有存在的必要踩寇,但它畢竟會消耗系統(tǒng)資源啄清。如果發(fā)起連接一方的 TIME_WAIT 狀態(tài)過多,占滿了所有端口資源俺孙,則會導致無法創(chuàng)建新連接辣卒。
- 客戶端受端口資源限制:如果客戶端 TIME_WAIT 過多,就會導致端口資源被占用睛榄,因為端口就65536個荣茫,被占滿就會導致無法創(chuàng)建新的連接;
- 服務(wù)端受系統(tǒng)資源限制:由于一個四元組表示TCP連接场靴,理論上服務(wù)端可以建立很多連接啡莉,服務(wù)端確實只監(jiān)聽一個端口港准,但是會把連接扔給處理線程,所以理論上監(jiān)聽的端口可以繼續(xù)監(jiān)聽咧欣。但是線程池處理不了那么多一直不斷的連接了浅缸。所以當服務(wù)端出現(xiàn)大量 TIME_WAIT時,系統(tǒng)資源被占滿時魄咕,會導致處理不過來新的連接衩椒;
怎么優(yōu)化TIME_WAIT
Linux 提供了 tcp_max_tw_buckets 參數(shù),當 TIME_WAIT 的連接數(shù)量超過該參數(shù)時哮兰,新關(guān)閉的連接就不再經(jīng)歷 TIME_WAIT 而直接關(guān)閉:
調(diào)整timewait最大個數(shù)
echo 5000>/proc/sys/net/ipv4/tcp_max_tw_buckets
當服務(wù)器的并發(fā)連接增多時毛萌,相應(yīng)地,同時處于 TIME_WAIT 狀態(tài)的連接數(shù)量也會變多喝滞,此時就應(yīng)當調(diào)大 tcp_max_tw_buckets 參數(shù)阁将,減少不同連接間數(shù)據(jù)錯亂的概率。
tcp_max_tw_buckets 也不是越大越好右遭,畢竟內(nèi)存和端口都是有限的
有一種方式可以在建立新連接時冀痕,復(fù)用處于 TIME_WAIT 狀態(tài)的連接,那就是打開 tcp_tw_reuse 參數(shù)狸演。但是需要注意言蛇,該參數(shù)是只用于客戶端(建立連接的發(fā)起方),因為是在調(diào)用connect() 時起作用的宵距,而對于服務(wù)端(被動連接方)是沒有用的腊尚。
打開tcp_tw_reuse功能
echo 1>/proc/sys/net/ipv4/tcp_tw_reuse
tcp_tw_reuse 從協(xié)議角度理解是安全可控的,可以復(fù)用處于 TIME_WAIT 的端口為新的連接所用满哪。
什么是協(xié)議角度理解的安全可控呢婿斥?主要有兩點:
- 只適用于連接發(fā)起方,也就是 C/S 模型中的客戶端哨鸭;
- 對應(yīng)的 TIME_WAIT 狀態(tài)的連接創(chuàng)建時間超過 1 秒才可以被復(fù)用民宿。