1. 程式人生 > >TCP握手和揮手

TCP握手和揮手

建立TCP連結,三次握手

第一次握手:建立連線時,客戶端傳送syn包(syn=j)到伺服器,並進入SYN_SENT狀態,等待伺服器確認;SYN:同步序列編號(Synchronize Sequence Numbers)。

第二次握手:伺服器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也傳送一個SYN包(syn=k),即SYN+ACK包,此時伺服器進入SYN_RECV狀態;

第三次握手:客戶端收到伺服器的SYN+ACK包,向伺服器傳送確認包ACK(ack=k+1),此包傳送完畢,客戶端和伺服器進入ESTABLISHED(TCP連線成功)狀態,完成三次握手。

完成三次握手,客戶端與伺服器開始傳送資料,在上述過程中,還有一些重要的概念:

未連線佇列

在三次握手協議中,伺服器維護一個未連線佇列,該佇列為每個客戶端的SYN包(syn=j)開設一個條目,該條目表明伺服器已收到SYN包,並向客戶發出確認,正在等待客戶的確認包。這些條目所標識的連線在伺服器處於SYN_RECV狀態,當伺服器收到客戶的確認包時,刪除該條目,伺服器進入ESTABLISHED狀態。

關閉TCP連線:改進的三次握手,四次揮手

對於一個已經建立的連線,TCP使用改進的三次握手來釋放連線(使用一個帶有FIN附加標記的報文段)。TCP關閉連線的步驟如下:

第一步,當主機A的應用程式通知TCP資料已經發送完畢時,TCP向主機B傳送一個帶有FIN附加標記的報文段(FIN表示英文finish)。主機A進入FIN_WAIT1狀態

第二步,主機B收到這個FIN報文段之後,並不立即用FIN報文段回覆主機A,而是先向主機A傳送一個確認序號ACK主機B進入CLOSE_WAIT狀態。同時通知自己相應的應用程式:對方要求關閉連線(先發送ACK的目的是為了防止在這段時間內,對方重傳FIN報文段)。主機A收到ACK報文後,進入FIN_WAIT2狀態。

第三步,主機B的應用程式告訴TCP:我要徹底的關閉連線,TCP向主機A送一個FIN報文段。主機B進入CLOSE狀態

第四步,主機A收到這個FIN報文段後,向主機B傳送一個ACK表示連線徹底釋放。主機A進入TIME_WAIT,TIME_WAIT裝填持續2*MSL時長,在linux體系中大概是60s,轉換成CLOSE狀態。

TIME_WAIT的狀態就是主動斷開的一方,傳送完最後一次ACK之後進入的狀態。

Q:
能不能傳送完ACK之後不進入TIME_WAIT就直接進入CLOSE狀態呢?

不行的,這個是為了TCP協議的可靠性,由於網路原因,ACK可能會發送失敗,那麼這個時候,被動一方會主動重新發送一次FIN,這個時候如果主動方在TIME_WAIT狀態,則還會再發送一次ACK,從而保證可靠性。那麼從這個解釋來說,2MSL的時長設定是可以理解的,MSL是報文最大生存時間,如果重新發送,一個FIN+一個ACK,再加上不定期的延遲時間,大致是在2MSL的範圍。

1.防止上一次連線中的包,迷路後重新出現,影響新連線(經過2MSL,上一次連線中所有的重複包都會消失)
2. 可靠的關閉TCP連線。在主動關閉方傳送的最後一個 ack(fin) ,有可能丟失,這時被動方會重新發fin, 如果這時主動方處於 CLOSED 狀態 ,就會響應 rst 而不是 ack。所以主動方要處於 TIME_WAIT 狀態,而不能是 CLOSED 。另外這麼設計TIME_WAIT 會定時的回收資源,並不會佔用很大資源的,除非短時間內接受大量請求或者受到攻擊。

當主動關閉連結的是伺服器端時,可能會出現TIME_WAIT狀態的TCP連線過多的情況。
在HTTP1.1協議中,有個 Connection 頭,Connection有兩個值,close和keep-alive,這個頭就相當於客戶端告訴服務端,服務端你執行完成請求之後,是關閉連線還是保持連線,保持連線就意味著在保持連線期間,只能由客戶端主動斷開連線。還有一個keep-alive的頭,設定的值就代表了服務端保持連線保持多久。
HTTP預設的Connection值為close,那麼就意味著關閉請求的一方几乎都會是由服務端這邊發起的。那麼這個服務端產生TIME_WAIT過多的情況就很正常了。
雖然HTTP預設Connection值為close,但是現在的瀏覽器傳送請求的時候一般都會設定Connection為keep-alive了。所以,也有人說,現在沒有必要通過調整引數來使TIME_WAIT降低了。
一般情況下TIME_WAIT過多的錯誤, 是調整tcp_max_tw_buckets, 這個值預設很大, 把這個值調小, 我的機器32核16G, 設定的是8192.
如果net.ipv4.tcp_tw_recycle=1, 那麼在為移動終端提供服務的時候, 伺服器接收到的TCP包多數是亂序的, 比如伺服器先收到了 tcp包為(201603290003), 後收到(201603290002), 也就是移動端先發的一個包後到達的伺服器, 那麼其中一個包很大概率會在短暫時間內被recycle, 後果可想而知.
一般這種包的亂序是因為行動網路不穩定造成的. 一般有網線直連的PC和其他網線直連裝置不出現此情況.
值 得一說的是,對於基於TCP的HTTP協議,關閉TCP連線的是Server端,這樣,Server端會進入TIME_WAIT狀態,可 想而知,對於訪 問量大的Web Server,會存在大量的TIME_WAIT狀態,假如server一秒鐘接收1000個請求,那麼就會積壓 240*1000=240,000個 TIME_WAIT的記錄,維護這些狀態給Server帶來負擔。當然現代作業系統都會用快速的查詢演算法來管理這些 TIME_WAIT,所以對於新的 TCP連線請求,判斷是否hit中一個TIME_WAIT不會太費時間,但是有這麼多狀態要維護總是不好。
HTTP協議1.1版規定default行為是Keep-Alive,也就是會重用TCP連線傳輸多個 request/response,一個主要原因就是發現了這個問題。

TIME_WAIT狀態可以通過優化伺服器引數得到解決,因為發生TIME_WAIT的情況是伺服器自己可控的,要麼就是對方連線的異常,要麼就是自己沒有迅速回收資源,總之不是由於自己程式錯誤導致的。但是CLOSE_WAIT就不一樣了,如果一直保持在CLOSE_WAIT狀態,那麼只有一種情況,就是在對方關閉連線之後伺服器程式自己沒有進一步發出FIN訊號。(參見4次揮手的第二步)。換句話說,就是在對方連線關閉之後,程式裡沒有檢測到,或者程式壓根就忘記了這個時候需要關閉連線,於是這個資源就一直
被程式佔著。個人覺得這種情況,通過伺服器核心引數也沒辦法解決,伺服器對於程式搶佔的資源沒有主動回收的權利,除非終止程式執行。
務器A是一臺爬蟲伺服器,它使用簡單的HttpClient去請求資源伺服器B上面的apache獲取檔案資源,正常情況下,如果請求成功,那麼在抓取完 資源後,伺服器A會主動發出關閉連線的請求,這個時候就是主動關閉連線,伺服器A的連線狀態我們可以看到是TIME_WAIT。如果一旦發生異常呢?假設 請求的資源伺服器B上並不存在,那麼這個時候就會由伺服器B發出關閉連線的請求,伺服器A就是被動的關閉了連線,如果伺服器A被動關閉連線之後程式設計師忘了 讓HttpClient釋放連線,那就會造成CLOSE_WAIT的狀態了。

相關問題

  • 為什麼是三次握手,兩次不行嗎

現假定一種異常情況,即A發出的第一個連線請求並沒有丟失,但是被阻塞了,以至於延誤到連結釋放後才到達B。本來這是個早已失效的報文段,但是B收到這個早已失效的報文段時,誤以為A又發出了一次新的連結請求,於是向A傳送確定請求,同意建立連結。假定不採用三次握手,那麼只要B傳送確認,新的連線就建立了。由於現在A並沒有發出建立連線的請求,不會理會B的確認。也不會向B傳送資料,但B以為新的運輸連結已經建立了,並一直等待A的資料,導致B的資源被浪費。

  • 為什麼連線的時候是三次握手,關閉的時候卻是四次握手?

因為當Server端收到Client端的SYN連線請求報文後,可以直接傳送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連線時,當Server端收到FIN報文時,很可能並不會立即關閉SOCKET,所以只能先回復一個ACK報文,告訴Client端,”你發的FIN報文我收到了”。只有等到我Server端所有的報文都發送完了,我才能傳送FIN報文,因此不能一起傳送。故需要四步握手。

https://www.cnblogs.com/yjf512/p/5327886.html

https://blog.csdn.net/qq_18425655/article/details/52163228

https://www.cnblogs.com/sunxucool/p/3449068.html