1、正常情況下,調用close(),產生的其中一個效果就是發送FIN,只有雙方都調用close(),才會出現正常的四次揮手。
2、如果是服務器,發起四次揮手是在關閉accept()返回的套接字,而不是socket()返回的套接字
3、Initiator=client,Receiver=server 情況:如果是服務器進入CLOSE_WAIT,而不發送FIN的話(也就是不調用close()),重新創建服務器需要等待一段時間bind才能成功,這個時間就是客戶端FIN_WAIT_2的超時時間,超時后客戶端發送RST給服務器。所以客戶端close(),服務器必須也執行close()
4、主動調用 close() 一方才會進入 TIME_WAIT
5、調用shutdown()並不會啟動四次揮手
斷開為什么需要四次握手:
TCP協議是一種面向連接的、可靠的、基於字節流的運輸層通信協議。TCP是全雙工模式,這就意味着,當主機1發出FIN
報文段時,只是表示主機1已經沒有數據要發送了,主機1告訴主機2,它的數據已經全部發送完畢了;但是,這個時候主機1還是可以接受來自主機2的數據;當主機2返回ACK
報文段時,表示它已經知道主機1沒有數據發送了,但是主機2還是可以發送數據到主機1的;當主機2也發送了FIN
報文段時,這個時候就表示主機2也沒有數據要發送了,就會告訴主機1,我也沒有數據要發送了,之后彼此就會愉快的中斷這次TCP連接。
為什么TIME_WAIT狀態需要經過2MSL(最大報文段生存時間)才能到CLOSE狀態
注:主動關閉一方會進入TIME_WAIT狀態,以下假設Client是主動方
1、如果Client直接CLOSED了,那么由於IP協議的不可靠性或者是其它網絡原因,導致Server沒有收到Client最后回復的ACK。那么Server就會在超時之后繼續發送FIN,此時由於Client已經CLOSED了,就找不到與重發的FIN對應的連接,最后Server就會收到RST而不是ACK,Server就會以為是連接錯誤把問題報告給高層。這樣的情況雖然不會造成數據丟失,但是卻導致TCP協議不符合可靠連接的要求。所以,Client不是直接進入CLOSED,而是要保持TIME_WAIT,保證能再次接收 FIN 並發送 ACK。(猜測: FIN超時時間應該小於MSL,否則Client等待2MSL,也可能接收不到FIN)
2、如果Client直接CLOSED,然后又再向Server發起一個新連接,我們不能保證這個新連接與剛關閉的連接的端口號是不同的。也就是說有可能新連接和老連接的端口號是相同的。一般來說不會發生什么問題,但是還是有特殊情況出現:假設新連接和已經關閉的老連接端口號是一樣的,如果前一次連接的某些數據仍然滯留在網絡中,這些延遲數據在建立新連接之后才到達Server,由於新連接和老連接的端口號是一樣的,又因為TCP協議判斷不同連接的依據是socket pair,於是,TCP協議就認為那個延遲的數據是屬於新連接的,這樣就和真正的新連接的數據包發生混淆了。所以TCP連接還要在TIME_WAIT狀態等待2倍MSL,這樣可以保證本次連接的所有數據都從網絡中消失。
服務端為了解決這個TIME_WAIT問題,可選擇的方式有三種:
Ø 服務器關閉的時候使用RST的方式(這是網友說的,我使用ESP8266做服務器,發送RST《發送RST的原因是調用 close() 時接收緩沖區還有未讀完的數據》還是會等待2MSL)
Ø 服務器close()發送RST而不是FIN
Ø 在創建socket,bind之前使能SO_REUSEADDR,然后close()(如果客戶端斷網了,假如服務器端沒有keepalive(或者心跳包),服務器端沒法知道客戶端斷網了,也就沒法執行close()函數,而是傻傻繼續等待客戶端的數據)------- 適用於服務器不用檢測客戶端是否存在
Ø 如果使用keepalive,客戶端斷開網絡(比如客戶端被拔了網線),服務器的keepalive機制可以檢測到,read()返回失敗,服務器再次創建時不需要等待2MSL ------- 適用於服務器需要檢測客戶端是否存在
對於TIME_WAIT的插曲:
當建立一個TCP連接時,服務器端會繼續用原有端口監聽,同時用這個端口與客戶端通信。而客戶端默認情況下會使用一個隨機端口與服務器端的監聽端口通信。有時候,為了服務器端的安全性,我們需要對客戶端進行驗證,即限定某個IP某個特定端口的客戶端。客戶端可以使用bind來使用特定的端口。對於服務器端,當設置了SO_REUSEADDR選項時,它可以在2MSL內啟動並listen成功。但是對於客戶端,當使用bind並設置SO_REUSEADDR時,如果在2MSL內啟動,雖然bind會成功,但是在windows平台上connect會失敗。而在linux上則不存在這個問題。(我的實驗平台:winxp, ubuntu7.10)
要解決windows平台的這個問題,可以設置SO_LINGER選項。SO_LINGER選項決定調用close時TCP的行為。SO_LINGER涉及到linger結構體,如果設置結構體中l_onoff為非0,l_linger為0,那么調用close時TCP連接會立刻斷開,TCP不會將發送緩沖中未發送的數據發送,而是立即發送一個RST報文給對方,這個時候TCP連接就不會進入TIME_WAIT狀態。如你所見,這樣做雖然解決了問題,但是並不安全。通過以上方式設置SO_LINGER狀態,等同於設置SO_DONTLINGER狀態。
套接字 SO_LINGER 選項可以規定 close() 的行為:
l_onoff為0,則該選項關閉,l_linger的值被忽略,等於缺省情況,close立即返回;
l_onoff為非0,l_linger為0,則套接口關閉TCP連接時,TCP將丟棄保留在套接口發送緩沖區中的任何數據並發送一個RST給對方,而不是通常的四分組終止序列,這避免了TIME_WAIT狀態;
l_onoff 為非0,l_linger為非0,當套接口關閉時內核將拖延一段時間(由l_linger決定)。如果套接口緩沖區中仍殘留數據,進程將處於睡眠狀態,直 到(a)所有數據發送完且被對方確認,之后進行正常的終止序列(描述字訪問計數為0)或(b)延遲時間到。此種情況下,應用程序檢查close的返回值是非常重要的,如果在數據發送完並被確認前時間到,close將返回EWOULDBLOCK錯誤且套接口發送緩沖區中的任何數據都丟失。close的成功返回僅告訴我們發送的數據(和FIN)已由對方TCP確認,它並不能告訴我們對方應用進程是否已讀了數據。如果套接口設為非阻塞的,它將不等待close完成