linux服務(wù)器開(kāi)發(fā)相關(guān)視頻解析:
10道經(jīng)典面試題的剖析琐簇, 技術(shù)方向如何決定職業(yè)方向
linux多線(xiàn)程之epoll原理剖析與reactor原理及應(yīng)用
c/c++ linux服務(wù)器開(kāi)發(fā)免費(fèi)學(xué)習(xí)地址:c/c++ linux后臺(tái)服務(wù)器高級(jí)架構(gòu)師
CLOSE_WAIT和TIME_WAIT是如何產(chǎn)生的埋酬?大量的CLOSE_WAIT和TIME_WAIT又有何隱患麻裳?本文將通過(guò)實(shí)踐角度來(lái)帶你揭開(kāi)CLOSE_WAIT和TIME_WAIT的神秘面紗壕探!遇事不決先祭此圖:
稍微補(bǔ)充一點(diǎn):TIME_WAIT到CLOSED,這一步是超時(shí)自動(dòng)遷移狞甚。
兩條豎線(xiàn)分別是表示:
1等曼、主動(dòng)關(guān)閉(active close)的一方
2、被動(dòng)關(guān)閉(passive close)的一方
網(wǎng)絡(luò)上類(lèi)似的圖有很多损离,但是有的細(xì)節(jié)不夠哥艇,有的存在誤導(dǎo)。有的會(huì)把兩條線(xiàn)分別標(biāo)記成Client和Server僻澎。給讀者造成困惑貌踏。對(duì)于斷開(kāi)連接這件事十饥,客戶(hù)端和服務(wù)端都能作為主動(dòng)方發(fā)起,也就是「主動(dòng)關(guān)閉」可以是客戶(hù)端祖乳,可以是服務(wù)端逗堵。而對(duì)端相應(yīng)的就是「被動(dòng)關(guān)閉」。不管誰(shuí)發(fā)起眷昆,狀態(tài)遷移如上圖蜒秤。
1. 耗盡的是誰(shuí)的端口?
首先解答初學(xué)者對(duì)socket的認(rèn)識(shí)存在一個(gè)常見(jiàn)的誤區(qū)亚斋。作為服務(wù)端作媚,不管哪個(gè)WAIT都不會(huì)耗盡客戶(hù)端的端口!
舉個(gè)例子伞访,我在某云的云主機(jī)上有個(gè)Server程序:echo_server掂骏。我啟動(dòng)它,監(jiān)聽(tīng)2605端口厚掷。然后我在自己的MacBook上用telnet去連接它弟灼。連上之后,在云主機(jī)上用 netstat -anp看一下:
[guodong@yun test] netstat -anp|grep2 605
tcp 000.0.0.0:26050.0.0.0:*LISTEN3354/./echo_server
tcp 0172.12.0.2:2605xx.xx.xx.xx:31559ESTABLISHED3354/./echo_server
xx.xx.xx.xx是我Macbook的本機(jī)IP
其中有兩條記錄冒黑,LISTEN的表示是我的echo_server監(jiān)聽(tīng)一個(gè)端口田绑。ESTABLISHED表示已經(jīng)有一個(gè)客戶(hù)端連接了。第三列的IP端口是我echo_server的(這個(gè)顯示IP是局域網(wǎng)的抡爹;第四列顯示的是客戶(hù)端的IP和端口掩驱,也就是我MacBook。
要說(shuō)明的是這個(gè)端口:31559是客戶(hù)端的冬竟。這個(gè)是建立連接時(shí)的MacBook分配的隨機(jī)端口欧穴。
我們看一下echo_server占用的fd。使用 ls /proc/3354/fd -l 命令查看泵殴,其中 3354是echo_server的pid:
0->?/dev/pts/6
1->?/dev/pts/6
2->?/dev/pts/6
3->?anon_inode:[eventpoll]
4->?socket:[674802865]
5->?socket:[674804942]
0涮帘,1,2是三巨頭(標(biāo)準(zhǔn)輸入笑诅,輸出调缨,錯(cuò)誤)自不必言。3是因?yàn)槲沂褂昧薳poll吆你,所以有一個(gè)epfd弦叶。
4其實(shí)就是我服務(wù)端監(jiān)聽(tīng)端口打開(kāi)的被動(dòng)套接字;
5就是客戶(hù)端建立連接到時(shí)候妇多,分配給客戶(hù)端的連接套接字伤哺。server程序只要給5這個(gè)fd寫(xiě)數(shù)據(jù),就相當(dāng)于返回?cái)?shù)據(jù)給客戶(hù)端砌梆。
服務(wù)端怎么會(huì)耗盡客戶(hù)端的端口號(hào)的默责。這里消耗的其實(shí)是服務(wù)端的fd(也不是端口)贬循!
【文章福利】需要C/C++ Linux服務(wù)器架構(gòu)師學(xué)習(xí)資料加群812855908(資料包括C/C++咸包,Linux桃序,golang技術(shù),Nginx烂瘫,ZeroMQ媒熊,MySQL,Redis坟比,fastdfs芦鳍,MongoDB,ZK葛账,流媒體柠衅,CDN,P2P籍琳,K8S菲宴,Docker,TCP/IP趋急,協(xié)程喝峦,DPDK,ffmpeg等)
2. 什么時(shí)候出現(xiàn)CLOSE_WAIT?
2.1 舉個(gè)例子
回到我的MacBook終端呜达,查看一下2605有關(guān)的連接(Mac上netstat不太好用谣蠢,只能用lsof了):
[guodong@MacBooktest]lsof-iTCP:2605
COMMANDPIDUSERFDTYPEDEVICESIZE/OFFNODENAME
telnet74131guodong3uIPv40x199db390a76b3eb30t0TCP192.168.199.155:50307->yy.yy.yy.yy:nsc-posa(ESTABLISHED)
yy.yy.yy.yy表示的遠(yuǎn)程云主機(jī)的IP
nsc-posa其實(shí)就是端口2605,因?yàn)?605也是某個(gè)經(jīng)典協(xié)議(NSC POSA)的默認(rèn)端口查近,所以這種網(wǎng)絡(luò)工具直接顯示成了那個(gè)協(xié)議的名稱(chēng)眉踱。
客戶(hù)端pid為74135。當(dāng)然霜威,我其實(shí)知道我是用telnet連接的谈喳,只是為了查pid的話(huà),ps aux|grep telnet也可以侥祭。
注意:為了測(cè)試叁执。我這里的echo_server是寫(xiě)的有問(wèn)題的鱼喉。就是沒(méi)有處理客戶(hù)端異常斷開(kāi)的事件跷究。
下面我kill掉telnet(kill -9 74131)坷虑。再回到云主機(jī)查看一下:
[guodong@yuntest]netstat-anp|grep2605
tcp000.0.0.0:26050.0.0.0:*LISTEN3354/./echo_server
tcp1172.12.0.2:2605xx.xx.xx.xx:31559CLOSE_WAIT3354/./echo_server
由于echo_server內(nèi)沒(méi)對(duì)連接異常進(jìn)行偵測(cè)和處理鲸沮。所以可以看到原先ESTABLISHED的連接變成了CLOSE_WAIT秩贰。并且會(huì)持續(xù)下去赔蒲。我們?cè)倏匆幌滤蜷_(kāi)的fd:
0->?/dev/pts/6
1->?/dev/pts/6
2->?/dev/pts/6
3->?anon_inode:[eventpoll]
4->?socket:[674865719]
5->?socket:[674865835]
5這個(gè)fd還存在矮燎,并且會(huì)一直存在毡证。所以當(dāng)有大量CLOSE_WAIT的時(shí)候會(huì)占用服務(wù)器的fd琼牧。而一個(gè)機(jī)器能打開(kāi)的fd數(shù)量是有限的恢筝。超過(guò)了哀卫,因?yàn)闊o(wú)法分配fd,就無(wú)法建立新連接啦撬槽!
2.2 怎么避免客戶(hù)端異常斷開(kāi)時(shí)的服務(wù)端CLOSE_WAIT此改?
有一個(gè)方法。比如我用了epoll侄柔,那么我監(jiān)聽(tīng)客戶(hù)端連接套接字(5)的EPOLLRDHUP這個(gè)事件共啃。當(dāng)客戶(hù)端意外斷開(kāi)時(shí),這個(gè)事件就會(huì)被觸發(fā)暂题,觸發(fā)之后移剪。我們針對(duì)性的對(duì)這個(gè)fd(5)執(zhí)行close()操作就可以了。改下代碼薪者,重新模擬一下上述流程纵苛,blabla細(xì)節(jié)略過(guò)。現(xiàn)在我們新echo_server啟動(dòng)言津。MacBook的telnet連接成功攻人。然后我kill掉了telnet。觀察一下云主機(jī)上的狀況:
[guodong@yuntest]netstat-anp|grep2605
tcp000.0.0.0:26050.0.0.0:*LISTEN7678/./echo_server
tcp1172.12.0.2:2605xx.xx.xx.xx:31559LAST_ACK
出現(xiàn)了LAST_ACK纺念。我們看下fd贝椿。命令:ls /proc/7678/fd -l
0->?/dev/pts/6
1->?/dev/pts/6
2->?/dev/pts/6
3->?anon_inode:[eventpoll]
4->?socket:[674905737]
fd(5)其實(shí)已經(jīng)關(guān)閉了。過(guò)一會(huì)我們重新netstat看下:
[guodong@yuntest]netstat-anp|grep2605
tcp000.0.0.0:26050.0.0.0:*LISTEN7678/./echo_server
LAST_ACK也消失了陷谱。為什么出現(xiàn)LAST_ACK烙博。翻到開(kāi)頭,看我那張圖把萄贰渣窜!
CLOSE_WAIT不會(huì)自動(dòng)消失,而LAST_TACK會(huì)超時(shí)自動(dòng)消失宪躯,時(shí)間很短乔宿,即使在其存續(xù)期內(nèi),fd其實(shí)也是關(guān)閉狀態(tài)访雪。實(shí)際我這個(gè)簡(jiǎn)單的程序详瑞,測(cè)試的時(shí)候不會(huì)每次都捕捉到LAST_WAIT。有時(shí)候用netstat 命令查看的時(shí)候臣缀,就是最終那副圖了坝橡。
3. 什么時(shí)候出現(xiàn)TIME_WAIT?
看我開(kāi)篇那個(gè)圖就知道了。
現(xiàn)在我kill掉我的echo_server!
[guodong@yuntest]netstat-anp|grep2605
tcp00172.17.0.2:2605xx.xx.xx.xx:51327TIME_WAIT
云主機(jī)上原先ESTABLISHED的那條瞬間變成TIME_WAIT了精置。
這個(gè)TIME_WAIT也是超時(shí)自動(dòng)消失的计寇。時(shí)間是2MSL。MSL是多長(zhǎng)?
cat/proc/sys/net/ipv4/tcp_fin_timeout
一般是60番宁。2MSL也就2分鐘元莫。在2分鐘之內(nèi),對(duì)服務(wù)端有啥副作用嗎蝶押?有踱蠢,但問(wèn)題不大。那就是這期間重新啟動(dòng)Server會(huì)報(bào)端口占用播聪。這個(gè)等待朽基,一方面是擔(dān)心對(duì)方收不到自己的確認(rèn)布隔,等對(duì)方重發(fā)FIN离陶。另一方面2MSL是報(bào)文的最長(zhǎng)生命周期,可以避免Server重啟(或其他Server綁同樣端口)接收到了上一次的數(shù)據(jù)衅檀。
當(dāng)然這個(gè)2MLS的等待招刨,也可以通過(guò)給socket添加選項(xiàng)(SO_REUSEADDR)的方式來(lái)避免。Server可以立即重啟(這樣Server的監(jiān)控進(jìn)程就可以放心的重新拉起Server啦)哀军。
通常情況下TIME_WAIT對(duì)服務(wù)端影響有限沉眶,而大量CLOSE_WAIT風(fēng)險(xiǎn)較高,但正確編寫(xiě)代碼基本可以避免杉适。為什么只說(shuō)通常情況呢谎倔?因?yàn)樯a(chǎn)環(huán)境是復(fù)雜的,一個(gè)服務(wù)通常會(huì)和多個(gè)下游服務(wù)用各種各樣的協(xié)議進(jìn)行通信猿推。TIME_WAIT和CLOSE_WAIT在一些異常條件下片习,還是會(huì)觸發(fā)的。
并不是說(shuō)TIME_WAIT就真的無(wú)風(fēng)險(xiǎn)蹬叭,其實(shí)無(wú)論是TIME_WAIT還是CLOSE_WAIT藕咏,永遠(yuǎn)記住當(dāng)你的服務(wù)出現(xiàn)這兩種現(xiàn)象的時(shí)候,它們只是某個(gè)問(wèn)題導(dǎo)致的結(jié)果秽五,而不是問(wèn)題本身孽查。有些網(wǎng)絡(luò)教程教你怎么調(diào)大這個(gè)或那個(gè)的OS系統(tǒng)設(shè)置,個(gè)人感覺(jué)只是治標(biāo)不治本坦喘。找到本質(zhì)原因盲再,避免TIME_WAIT和CLOSE_WAIT的產(chǎn)生,才是問(wèn)題解決之道瓣铣!
把這些了解清楚時(shí)候答朋,是不是可以輕松應(yīng)對(duì)什么4次揮手之類(lèi)的面試題了?