TCP鏈接的創建和關閉

先立一個 flag,寫這篇文章是由於好久以前遇到的幾個坑,最近纔有時間去琢磨解決它們,有一些預備知識總結下丟在這,實話說,大學計算機網絡的內容感受幾乎已經全還回去了。。。緩存

TCP報文段的格式

TCP報文段的格式

TCP 報文段分爲 TCP 首部和數據部分,從課本上扒拉下來一張圖。學習 TCP 鏈接的創建和關閉,咱們主要關注的是和6個控制位。服務器

序號

佔 4 字節,序號範圍是[0, 2^32-1],共 2^32 個序號。序號增長到 2^32-1 後,下一個序號就又回到 0。也就是說,序號使用 mod 2^32 運算。 TCP是面向字節流的。在一個TCP鏈接中傳送的字節流中的每個字節都按順序編號。整個要傳送的字節流的起始序號必須在鏈接創建時設置。首部中的序號字段值則指的是本報文段所發送的數據的第一個字節的序號。例如,一報文段的序號是 301,而攜帶的數據共有 100 字節。這就代表:本報文段的數據的第一個字節的序號是 301,最後一個字節的序號是 400。顯然,下一個報文段(若是還有的話)的數據序號應當從 401 開始,即下一個報文段的序號字段值應爲 401。這個字段的名稱也叫作「報文段序號」。網絡

上邊這段話是摘自《計算機網絡》第5版,描述的是該字段在數據傳輸時的用法,至於鏈接創建和關閉時,有另外的規則,下邊再進行討論。確認號也是,再也不復述。學習

本篇文章基本上是把《計算機網絡》第5版上的內容敲了一遍,加強我的記憶。計算機網絡

確認號

佔 4 字節,是指望收到對方下一個報文段的第一個數據字節的序號。例如,B 正確收到了 A 發送過來的一個報文段,其序號值是 501,而數據長度是 200 字節,(序號501 - 700),這代表 B 正確收到了 A 發送的到序號 700 爲止的數據。所以,B 指望收到 A 的下一個數據序號是 701,因而 B 在發送給 A 的確認報文段中把確認號置爲 701。指針

因爲序號字段有 32 位長,可對 4GB 的數據進行編號。在通常狀況下可保證當序號重複使用時,舊序號早已經過網絡到達終點了。three

緊急URG(URGent)

當 URG = 1時,表名緊急指針字段有效。它告訴系統此報文段中有緊急數據,應儘快傳送(至關於高優先級的數據),而不須要按原來的排隊順序來傳送。例如,已經發送了很長的一個程序要在遠地的主機上運行。但後來發現了一些問題,須要取消該程序的運行。所以用戶從鍵盤發出終端命令(Control + C)。若是不適用緊急數據,那麼這兩個字符將存儲在接收 TCP 的緩存末尾。只有在全部的數據被處理完畢後這兩個字符才被交付到接收方的應用進程。這樣作就浪費了許多時間。進程

當 URG 置 1 時,發送應用進程就告訴發送方的 TCP 有緊急數據要傳送。因而發送方 TCP 就把緊急數據插入到本報文段數據的最前面,而在緊急數據後面的數據還是普通數據。這時要與首部中緊急指針(Urgent Pointer)字段配合使用。圖片

確認 ACK(ACKnowlegment)

僅當 ACK = 1 時確認號字段纔有效。當 ACK = 0 時,確認號無效。TCP 規定,在鏈接創建後全部傳送的報文段都必須把 ACK 置 1。資源

推送 PSH (PuSH)

當兩個應用進程進行交互的通訊時,有時在一端的應用進程但願在鍵入一個命令後當即就可以收到對方的響應。在這種狀況下, TCP 就可使用推送(push)操做。這時,發送方 TCP 把 PSH 置 1,並當即建立一個報文段發送出去。接收方 TCP 收到 PSH = 1 的報文段,就儘快地(即「推送」向前)交付給接收應用進程,而再也不等整個緩存都填滿了後再向上交付。

雖然應用程序能夠選擇推送操做,但推送操做還不多使用。

復位 RST(ReSeT)

當 RST = 1時,代表 TCP 鏈接中出現嚴重差錯(如因爲主機崩潰或其餘緣由),必須釋放鏈接,而後再從新創建運輸鏈接。RST 置 1 還用來拒絕一個非法的報文段或拒絕打開一個鏈接。 RST 也可稱爲重建位或重置位。

同步 SYN(SYNchronization)

在鏈接創建時用來同步序號。當 SYC = 1 而 ACK = 0 時,代表這是一個鏈接請求報文段。對方若贊成創建鏈接,則應在響應的報文段中使 SYN = 1 和 ACK = 1。所以,SYN 置爲 1 就表示這是一個鏈接請求或鏈接接受報文。

終止 FIN

用來釋放一個鏈接。當 FIN = 1 時,代表此報文的發送方數據已發送完畢,並要求釋放運輸鏈接。

TCP 鏈接的創建

輸入圖片說明 輸入圖片說明

如圖,假定主機 A 運行的是 TCP 客戶端程序,而 B 運行 TCP 服務器程序。最初兩端的 TCP 進程都處於 CLOSED 狀態。圖中在主機下面的方框分別是 TCP 進程所處的狀態。請注意,A 主動打開鏈接,而 B 被動打開鏈接。

B 的 TCP 服務器進程先建立傳輸控制塊 TCB,準備接受客戶進程的鏈接請求。而後服務器進程就處於 LISTEN 狀態,等待客戶的鏈接請求。若有,即做出相應。

A 的 TCP 客戶進程也是首先建立傳輸控制塊 TCB,而後向 B 發出鏈接請求報文段。這時首部中的同步位 SYN = 1,同時選擇一個初始序號 seq = x。TCP 規定,SYN 報文段(即 SYNC = 1 的報文段)不能攜帶數據,但要消耗掉一個序號。這時,TCP 客戶進程進入 SYN-SENT 狀態。

B 收到鏈接請求報文段後,若是贊成創建鏈接,則向 A 發送確認。在確認報文段中應把 SYN 和 ACK 位都置 1,確認號是 ack = x + 1,同時也爲本身選擇一個初始序號 seq = y。請注意,這個報文段也不能攜帶數據,但一樣要消耗掉一個序號。這時, TCP 客戶進程進入 SYN-RCVD 狀態。

TCP 客戶進程收到 B 的確認後,還要向 B 給出確認。確認報文段的 ACK 置 1,確認號 ack = y + 1,而本身的序號 seq = x + 1。TCP 的標準規定, ACK 報文段能夠攜帶數據。但若是不攜帶數據則不消耗序號,在這種狀況下,下一個數據報文段的序號還是 seq = x + 1。這時,TCP 鏈接已經創建,A 進入 ESTABLISHED 狀態。

當 B 收到 A 的確認後,也進入 ESTABLISHED 狀態。

這就是三次握手(three-way handshake)的過程。

爲何 A 還要發送一次確認呢?這主要是爲了防止已失效的鏈接請求報文段忽然又傳到了 B,於是產生錯誤。

所謂「已失效的鏈接請求報文段」是這樣產生的,考慮一種正常狀況:A 發出鏈接請求,但因鏈接請求報文丟失而未收到確認。因而 A 再重傳一次鏈接請求。後來收到了確認,創建了鏈接,數據傳輸完畢後,就釋放了鏈接。A 共發送了兩個鏈接請求報文段,其中第一個丟失,第二個到達了 B。沒有「已失效的鏈接請求報文段」。

現假定出現一種異常狀況,即 A 發出的第一個鏈接請求報文段並無丟失,而是在某些網絡節點長時間滯留了,以至延誤到鏈接釋放後的某個時間纔到達 B。原本這是一個早已失效的報文段。但 B 收到此失效的鏈接請求報文段後,就誤認爲是 A 又發出一次新的鏈接請求。因而就向 A 發出確認報文段,贊成創建鏈接。假定不採用三次握手,那麼只要 B 發出確認,新的鏈接就創建了。

因爲如今 A 並無發出創建鏈接的請求,所以不會理睬 B 的確認,也不會向 B 發送數據,但 B 卻覺得新的運輸鏈接已經創建了,並一直等待 A 發來數據。 B 的許多資源就這樣白白浪費了。

採用三次握手的辦法,在上述狀況下,A 不會向 B 的確認發出確認。 B 因爲收不到確認,就知道 A 並無要求創建鏈接。

TCP 的鏈接釋放

輸入圖片說明

數據傳輸結束後,通訊的雙方均可釋放鏈接。如今 A 和 B 都處於 ESTABLISHED 狀態。A 的應用進程先向其 TCP 發出鏈接釋放報文段,並中止再發送數據,主動關閉 TCP 鏈接。 A 把鏈接釋放報文段首部的 FIN 置 1,其序號 seq = u,它等於前面已傳送過的數據最後一個字節的序號加 1。這時 A 進入 FIN-WAIT-1 狀態,等待 B 的確認。請注意,TCP 規定,FIN 報文段即便不攜帶數據,它也消耗掉一個序號。

B 收到鏈接釋放報文段後即發出確認,確認號是 ack = u + 1,而這個報文段本身的序號是 v,等於 B 前面已傳送過數據的最後一個字節的序號加 1。而後 B 就進入 CLOSE_WAIT 狀態。 TCP 服務器進程這時應通知高層應用進程,於是從 A 到 B 這個方向的鏈接就釋放了,這時的 TCP 鏈接處於半關閉狀態,即 A 已經沒有數據要發送了,但 B 若發送數據,A 仍要接收。也就是說,從 B 到 A 這個方向的鏈接並未關閉。這個狀態可能會持續一些時間。

A 收到來自 B 的確認後,就進入 FIN-WAIT-2 狀態,等待 B 發出的鏈接釋放報文段。

若 B 已經沒有要向 A 發送的數據,其應用進程就通知 TCP 釋放鏈接。這時 B 發出的鏈接釋放報文段必須使 FIN = 1。現假定 B 的序號爲 w(在半關閉狀態 B 可能又發送了一些數據)。B 還必須重複上次已發送過的確認號 ack = u + 1。這時 B 就進入了 LAST-ACK 狀態,等待 A 的確認。

那麼若是沒有要發送的數據,B 的序號爲多少呢?書中沒有寫,可是根據 wireshark 抓包的狀況來看,B 的序號仍爲 v。 輸入圖片說明

A 在收到 B 的鏈接釋放報文段後,必須對此發出確認。在確認報文段中把 ACK 置 1,確認號 ack = w + 1,而本身的序號是 seq = u + 1(根據TCP標準,前面發送過的FIN報文段要消耗一個序號)。而後進入到 TIME-WAIT 狀態。請注意,如今 TCP 鏈接尚未釋放掉,必須通過時間等待期(TIME-WAIT timer)設置的時間 2MSL 後,A 才進入到 CLOSED 狀態。時間 MSL 叫作最長報文段壽命(Maximum Segment Lifetime),RFC 793 建議設爲 2 分鐘。但這徹底是從工程上來考慮,對於如今的網絡, MSL = 2 分鐘可能太長了一些。所以 TCP 容許不一樣的實現可根據具體狀況使用更小的 MSL 值。所以,從 A 進入到 TIME-WAIT 狀態後,要通過 4 分鐘才能進入到 CLOSED 狀態,才能開始創建下一個新的鏈接。當 A 撤銷相應的傳輸控制塊 TCB 後,就結束了此次的 TCP 鏈接。

爲何 A 在 TIME-WAIT 狀態必須等待 2MSL 的時間呢?這有兩個理由。

  1. 爲了保證 A 發送的最後一個 ACK報文段可以到達 B。這個 ACK 報文段有可能丟失,於是使處在 LAST-ACK 狀態的 B 收不到對已發送的 FIN + ACK 報文段的確認。 B 會超時重傳這個 FIN + ACK 報文段,而 A 就能在 2MSL 時間內收到這個重傳的 FIN + ACK 報文段。接着 A 重傳一次確認,從新啓動 2MSL 計時器。最後, A 和 B 都正常進入到 CLOSED 狀態。若是 A 在 TIME-WAIT 狀態不等待一段時間,而是在發送完 ACK 報文段後當即釋放鏈接,那麼久沒法收到 B 重傳的 FIN + ACK 報文段,於是也不會再發送一次確認報文段。這樣,B 就沒法按照正常步驟進入 CLOSED 狀態。
  2. 防止上一節提到的「已失效的鏈接請求報文段」出如今本鏈接中。A 在發送完最後一個 ACK 報文段後,再通過 2MSL,就可使本鏈接持續的時間內所產生的全部報文段都從網絡中消失。這樣就可使下一個新的鏈接不會出現這種舊的鏈接請求報文段。

B 只要收到了 A 發出的確認,就進入 CLOSED 狀態。一樣,B 在撤銷相應的傳輸控制塊 TCB 後,就結束了此次的 TCP 鏈接。咱們注意到, B結束 TCP 鏈接的時間要比 A 早一些。

上述的 TCP 鏈接釋放過程是四次握手,但也能夠當作是兩個二次握手。

除時間等待器外, TCP 還設有一個保活計時器(keepalive timer)。設想有這樣的狀況:客戶已主動與服務器創建了 TCP 鏈接。但後來客戶端的主機忽然出故障。顯然,服務器之後就不能再收到客戶發來的數據。所以,應當有措施使服務器不要再白白等待下去。這就是保活計時器。服務器每收到一次客戶的數據,就從新設置保活計時器,時間的設置一般是兩小時。若兩小時沒有收到客戶的數據,服務器就發送一個探測報文段,之後則每隔 75 秒發送一次。若一連發送 10 個探測報文段後仍無客戶的響應,服務器就認爲客戶端出了故障,接着就關閉了這個鏈接。

相關文章
相關標籤/搜索