1. 程式人生 > >詳解TCP三次握手與四次揮手

詳解TCP三次握手與四次揮手

一、TCP三次握手和socket詳解

1.TCP連線

第一次:cli傳送SYN包(SYN = j)到ser,並且進入SYN_SEND狀態,等待伺服器確認;

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

第三次:cli收到ser的SYN+ACK包,向ser傳送確認包ACK(ACK = k+1),此包傳送完畢,ser和cli進入ESTABLISHED狀態,完成三次握手。

握手過程中傳送的包裡不包含資料,三次握手完畢之後,cli與ser才開始傳送資料。

2.三次握手狀態詳解:

3.圖解三次握手:

從圖中可以看出,當客戶端呼叫connect時,觸發了連線請求,向伺服器傳送了SYN包,這時connect進入阻塞狀態;伺服器監聽到連線請求,即收到了SYN J包,呼叫accept函式接收請求 向客戶端傳送SYN K,ACK J+1,這時accept進入阻塞狀態;客戶端收到伺服器的SYN K,ACK J+1之後,這時connect返回,並且對SYN K進行確認;伺服器收到ACK K+1,accept返回,至此三次握手完畢,連線建立。客戶端的connect在三次握手的第二次返回,而伺服器端的accept在三次握手的第三次返回

套接字之間的連線過程分為三個步驟:伺服器監聽、客戶端請求、連線確認

(1)伺服器監聽:是伺服器端套接字並不指定具體的客戶端套接字,而是一直處於等待的狀態,實時監控網路狀態。

(2)客戶端請求:是指由客戶端的套接字提出連線請求,要連線的目標是伺服器端的套接字。為此,客戶端的套接字必須首先描述它要連線的伺服器的套接字,指出伺服器端套接字的地址和埠號,然後就向伺服器端套接字提出連線請求。

(3)連線確認:是指當伺服器端套接字監聽到或者接收到客戶端套接字的連線請求,它就響應該請求,建立一個新執行緒,把伺服器端套接字的描述發給客戶端,一旦客戶端確認此描述,連線就建立好了。注意:此時,伺服器端套接字繼續處於監聽狀態,繼續接收其他客戶端套接字的連線請求。

二、圖解TCP四次揮手

第一次揮手:客戶端程序首先呼叫close主動關閉連線,這時客戶端向伺服器傳送一個FIN報文段,此時,客戶端進入FIN_WAIT_1狀態,這表示客戶端已無資料要傳送給伺服器;

第二次揮手:伺服器收到了客戶端傳送的FIN報文段,向客戶端回覆一個ACK報文段,ACK+1,客戶端進入FIN_WAIT_2狀態,伺服器告訴客戶端,我同意你的關閉請求;

第三次揮手:伺服器向客戶端傳送FIN報文段,請求關閉連線,同時伺服器進入LAST_ACK狀態;

第四次揮手:客戶端收到伺服器傳送的FIN報文段,向伺服器傳送ACK報文段,然後客戶端進入TIME_WAIT狀態,伺服器收到客戶端的ACK報文段之後,就關閉連線,此時客戶端等待2MSL後依舊沒有收到回覆,則證明伺服器端已正常關閉,那好,客戶端也可以關閉連線了。

三、疑難問題

1、FIN_WAIT_1和FIN_WAIT_2的解析

FIN_WAIT_1: 這個狀態要好好解釋一下,事實上FIN_WAIT_1和FIN_WAIT_2狀態的真正含義都是表示等待對方的FIN報文。而這兩種狀態的差別是:FIN_WAIT_1狀態實際上是當SOCKET在ESTABLISHED狀態時,它想主動關閉連線,向對方傳送了FIN報文,此時該SOCKET即進入到FIN_WAIT_1狀態。

而當對方迴應ACK報文後,則進入到FIN_WAIT_2狀態。當然在實際的正常情況下,不管對方何種情況下,都應該立即迴應ACK報文,所以FIN_WAIT_1狀態通常是比較難見到的。而FIN_WAIT_2狀態還有時經常能夠用netstat看到。


FIN_WAIT_2:上面已經詳解了這樣的狀態,實際上FIN_WAIT_2狀態下的SOCKET。表示半連線,也即有一方要求close連線,但另外還告訴對方,我臨時還有點資料須要傳送給你,稍後再關閉連線。
TIME_WAIT: 表示收到了對方的FIN報文。併發送出了ACK報文,就等2MSL後就可以回到CLOSED可用狀態了。假設FIN_WAIT_1狀態下。收到了對方同一時候帶FIN標誌和ACK標誌的報文時,能夠直接進入到TIME_WAIT狀態,而無須經過FIN_WAIT_2狀態。

2、為什麼握手是三次,揮手是四次?

這是由於服務端的LISTEN狀態下的SOCKET當收到SYN報文的建連請求後。它能夠把ACK和SYN(ACK起應答作用。而SYN起同步作用)放在一個報文裡來發送。但關閉連線時,當收到對方的FIN報文通知時,它只表示對方沒有資料傳送給你了。但未必你所有的資料都所有傳送給對方了。所以你能夠未必會立即會關閉SOCKET,也即你可能還須要傳送一些資料給對方之後,再發送FIN報文給對方來表示你允許如今能夠關閉連線了。所以它這裡的ACK報文和FIN報文多數情況下都是分開發送的。

3、為什麼TIME_WAIT狀態還須要等2MSL後才幹返回到CLOSED狀態?

MSL即報文最大生存時間, MSL是不論什麼報文段被丟棄前在網路內的最長時間。2MSL也就是這個時間的2倍,當TCP連線完畢四個報文段的交換時,主動關閉的一方將繼續等待一定時間(2-4分鐘),即使兩端的應用程式結束。

為什麼須要這個2MSL呢?
(1)儘管兩方都允許關閉連線了,並且握手的4個報文也都協調和傳送完成,按理能夠直接回到CLOSED狀態;可是由於我們必需要假想網路是不可靠的,你無法保證你最後傳送的ACK報文會一定被對方收到,因此對方處於LAST_ACK狀態下的SOCKET可能會由於超時未收到ACK報文,而重發FIN報文,所以這個TIME_WAIT狀態的作用就是用來重發可能丟失的ACK報文。
(2)報文可能會被混淆。意思是說。其它時候的連線可能會被當作本次的連線。當某個連線的一端處於TIME_WAIT狀態時。該連線將不能再被使用。其實,對於我們比較有現實意義的是,這個port將不能再被使用。某個port處於TIME_WAIT狀態(其實應該是這個連線)時,這意味著這個TCP連線並沒有斷開(全然斷開),那麼。假設你bind這個port,就會失敗。對於server而言,假設server突然crash掉了,那麼它將無法在2MSL內又一次啟動,由於bind會失敗。解決問題的一個方法就是設定socket的SO_REUSEADDR選項。這個選項意味著你能夠重用一個地址。
當建立一個TCP連線時。server端會繼續用原有port監聽,同一時候用這個port與client通訊。

而client預設情況下會使用一個隨機port與server端的監聽port通訊。

有時候,為了server端的安全性,我們須要對client進行驗證,即限定某個IP某個特定port的client。client能夠使用bind來使用特定的port。對於server端,當設定了SO_REUSEADDR選項時。它能夠在2MSL內啟動並listen成功。可是對於client。當使用bind並設定SO_REUSEADDR時,假設在2MSL內啟動,bind會成功。

當TCP連線發生一些物理上的意外情況時,比如網線斷開,linux上的TCP實現會依舊覺得該連線有效。

4、為什麼不能兩次握手?

3次握手完畢兩個重要的功能。既要兩方做好傳送資料的準備工作(兩方都知道彼此已準備好),也要同意兩方就初始序列號進行協商,這個序列號在握手過程中被髮送和確認。

如今把三次握手改成僅須要兩次握手。死鎖是可能發生的。

Eg:假定cli給ser傳送一個連線請求分組,ser收到了這個分組,併發送了確認應答分組。依照兩次握手的協定,ser覺得連線已經成功地建立了,能夠開始傳送資料分組。但是,cli在ser的應答分組在傳輸中被丟失的情況下,將不知道ser是否已準備好。不知道ser建立什麼樣的序列號。cli甚至懷疑ser是否收到自己的連線請求分組。在這樣的情況下,cli覺得連線還未建立成功,將忽略ser發來的不論什麼資料分組,僅僅等待連線確認應答分組。而S在發出的分組超時後,反覆傳送相同的分組。這樣就形成了死鎖

5、time_wait狀態如何產生?
首先呼叫close()發起主動關閉的一方,在傳送最後一個ACK之後會進入time_wait的狀態,也就說該傳送方會保持2MSL時間之後才會回到初始狀態。MSL值得是資料包在網路中的最大生存時間。產生這種結果使得這個TCP連線在2MSL連線等待期間,定義這個連線的四元組(客戶端IP地址和埠,服務端IP地址和埠號)不能被使用。

6、time_wait狀態產生的原因與作用

(1)為實現TCP全雙工連線的可靠釋放

由TCP狀態變遷圖可知,假設發起主動關閉的一方(client)最後傳送的ACK在網路中丟失,由於TCP協議的重傳機制,執行被動關閉的一方(server)將會重發其FIN,在該FIN到達client之前,client必須維護這條連線狀態,也就說這條TCP連線所對應的資源(client方的local_ip,local_port)不能被立即釋放或重新分配,直到另一方重發的FIN達到之後,client重發ACK後,經過2MSL時間週期沒有再收到另一方的FIN之後,該TCP連線才能恢復初始的CLOSED狀態。如果主動關閉一方不維護這樣一個TIME_WAIT狀態,那麼當被動關閉一方重發的FIN到達時,主動關閉一方的TCP傳輸層會用RST包響應對方,這會被對方認為是有錯誤發生,然而這事實上只是正常的關閉連線過程,並非異常。

(2)為使舊的資料包在網路因過期而消失

為說明這個問題,我們先假設TCP協議中不存在TIME_WAIT狀態的限制,再假設當前有一條TCP連線:(local_ip, local_port, remote_ip,remote_port),因某些原因,我們先關閉,接著很快以相同的四元組建立一條新連線。本文前面介紹過,TCP連線由四元組唯一標識,因此,在我們假設的情況中,TCP協議棧是無法區分前後兩條TCP連線的不同的,在它看來,這根本就是同一條連線,中間先釋放再建立的過程對其來說是“感知”不到的。這樣就可能發生這樣的情況:前一條TCP連線由local peer傳送的資料到達remote peer後,會被該remot peer的TCP傳輸層當做當前TCP連線的正常資料接收並向上傳遞至應用層(而事實上,在我們假設的場景下,這些舊資料到達remote peer前,舊連線已斷開且一條由相同四元組構成的新TCP連線已建立,因此,這些舊資料是不應該被向上傳遞至應用層的),從而引起資料錯亂進而導致各種無法預知的詭異現象。作為一種可靠的傳輸協議,TCP必須在協議層面考慮並避免這種情況的發生,這正是TIME_WAIT狀態存在的第2個原因。

 

7、大量TIME_WAIT狀態造成的後果

在生產過程中,如果伺服器使用短連線,那麼完成一次請求後會主動斷開連線,就會造成大量time_wait狀態。因此我們常常在系統中會採用長連線,減少建立連線的消耗,同時也減少TIME_WAIT的產生,但實際上即使使用長連線配置不當時,當TIME_WAIT的生產速度遠大於其消耗速度時,系統仍然會累計大量的TIME_WAIT狀態的連線。TIME_WAIT狀態連線過多就會造成一些問題。如果客戶端的TIME_WAIT連線過多,同時它還在不斷產生,將會導致客戶端埠耗盡,新的埠分配不出來,出現錯誤。如果伺服器端的TIME_WAIT連線過多,可能會導致客戶端的請求連線失敗

高併發短連線TCP伺服器上,當伺服器處理完請求後立刻主動正常關閉連線。這個場景下會出現大量socket處於TIME_WAIT狀態。如果客戶端的併發量持續很高,此時部分客戶端就會顯示連線不上。
我來解釋下這個場景。主動正常關閉TCP連線,都會出現TIMEWAIT

為什麼我們要關注這個高併發短連線呢?有兩個方面需要注意:
1. 高併發可以讓伺服器在短時間範圍內同時佔用大量埠,而埠有個0~65535的範圍,並不是很多,刨除系統和其他服務要用的,剩下的就更少了。
2. 在這個場景中,短連線表示業務處理+傳輸資料的時間 遠遠小於 TIMEWAIT超時的時間的連線

  這裡有個相對長短的概念,比如取一個web頁面,1秒鐘的http短連線處理完業務,在關閉連線之後,這個業務用過的埠會停留在TIMEWAIT狀態幾分鐘,而這幾分鐘,其他HTTP請求來臨的時候是無法佔用此埠的(佔著茅坑不拉翔)。單用這個業務計算伺服器的利用率會發現,伺服器幹正經事的時間和埠(資源)被掛著無法被使用的時間的比例是 1:幾百,伺服器資源嚴重浪費。(說個題外話,從這個意義出發來考慮伺服器效能調優的話,長連線業務的服務就不需要考慮TIMEWAIT狀態。同時,假如你對伺服器業務場景非常熟悉,你會發現,在實際業務場景中,一般長連線對應的業務的併發量並不會很高
 綜合這兩個方面,持續的到達一定量的高併發短連線,會使伺服器因埠資源不足而拒絕為一部分客戶服務。同時,這些埠都是伺服器臨時分配,無法用SO_REUSEADDR選項解決這個問題。

8、查詢TCP連線數

9、怎樣解決TIME_WAIT過多的情況

1)修改系統配置
  需要修改的tcp_max_tw_bucketstcp_tw_recycletcp_tw_reuse這三個配置項。
         1)將tcp_max_tw_buckets調大,從本文第一部分可知,其預設值為18w(不同核心可能有所不同,需以機器實際配置為準),根據文件,我們可以適當調大,至於上限是多少,文件沒有給出說明,我也不清楚。個人認為這種方法只能對TIME_WAIT過多的問題起到緩解作用,隨著訪問壓力的持續,該出現的問題遲早還是會出現,治標不治本。
         2)開啟tcp_tw_recycle選項:在shell終端輸入命令”echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle”可以開啟該配置。
   需要明確的是:其實TIME_WAIT狀態的socket是否被快速回收是由tcp_tw_recycletcp_timestamps兩個配置項共同決定的,只不過由於tcp_timestamps預設就是開啟的,故大多數文章只提到設定tcp_tw_recycle1
   還需要特別注意的是:clientserver之間有如NAT這類網路轉換裝置時,開啟tcp_tw_recycle選項可能會導致serverdrop(直接傳送RST)來自clientSYN包。

         3)開啟tcp_tw_reuse選項:echo1 > /proc/sys/net/ipv4/tcp_tw_reuse。該選項也是與tcp_timestamps共同起作用的,另外socket reuse也是有條件的。查了很多資料,與在用到NATFireWall的網路環境下開啟tcp_tw_recycle後可能帶來副作用相比,貌似沒有發現tcp_tw_reuse引起的網路問題。
2修改應用程式
         1)將TCP短連線改造為長連線。通常情況下,如果發起連線的目標也是自己可控制的伺服器時,它們自己的TCP通訊最好採用長連線,避免大量TCP短連線每次建立/釋放產生的各種開銷;如果建立連線的目標是不受自己控制的機器時,能否使用長連線就需要考慮對方機器是否支援長連線方式了。
        2)通過getsockopt/setsockoptapi設定socketSO_LINGER選項。

3)需要補充說明的問題
   我們說TIME_WAIT過多可能引起無法對外建立新連線,其實有一個例外但比較常見的情況:S模組作為WebServer部署在伺服器上,繫結本地某個埠;客戶端與S間為短連線,每次互動完成後由S主動斷開連線。這樣,當客戶端併發訪問次數很高時,S模組所在的機器可能會有大量處於TIME_WAIT狀態的TCP連線。但由於伺服器模組綁定了埠,故在這種情況下,並不會引起由於TIME_WAIT過多導致無法建立新連線的問題。也就是說,本文討論的情況,通常只會在每次由作業系統分配隨機埠的程式執行的機器上出現(每次分配隨機埠,導致後面無埠可用)。