淺談TCP連接的創建和銷燬過程

1. 概述

本文主要講述的內容主要包含以下部分

  • TCP的概念
  • 連接的建立過程
  • 連接的拆除過程

2. TCP協議

我們首先要明確一點,這裏討論的TCP協議並不是指TCP/IP協議簇,而是單指TCP協議,即Transmission Control Protocol,指傳輸控制協議,位於傳輸層(OSI七層模型的第四層,或是TCP/IP四層協議的第三層)

都應該清楚,傳輸層負責報文從進程到進程的傳遞,也可以說是從端口到端口的協議,所以TCP協議也屬於一種端對端的協議。常用的端口號有:

  • 20:FTP(文件傳輸協議)的數據傳輸
  • 25:SMTP(簡單郵件傳輸協議)
  • 53:DNS(域名系統)
  • 80:HTTP(超文本傳輸協議)
  • 111:RPC(遠程過程調用)

TCP是一個面向流的協議,允許發送進程以字節流的方式傳遞數據,同樣地,接收方也以字節流的方式接收數據,這就存在了一個抽象的“管道”,管道的建立與拆除,就對應着TCP連接的創建和銷燬,在接下來我們就來詳細講解這一過程

3. TCP分組

在講解具體的過程之前,先來做一些準備工作。首先,TCP是一個全雙工的通信協議,也就是連接的雙方都能夠發送和接收數據,這些數據,也就是字節,TCP會對其進行編號,這是因爲TCP需要保證接收數據的順序。編好號之後,TCP會爲發送的每一個段分配一個序號,實際就是段中第一個字節的編號

發送的段,在TCP中也叫做分組,分組首部(首部不包含數據)的格式是如下這樣的:TCP分組
源端口、目的端口、序號和校驗和的概念相信應該大家都能理解,這裏就不再展開了,數據偏移、保留部分、緊急指針、選項,以及填充部分相對來說不太重要,感興趣的可以自行了解。剩餘相對重要的確認號、控制位(URG/ACK/PSH…)和窗口中,我們這裏指講和本文相關的前兩種

如果瞭解過數據鏈路控制的相關知識,應該會知道ack的概念,就相當於我們這裏的確認號,指的是我們期待接收到的分組的序號,因爲我們發送的每一個分組都是爲了期待對方給我們迴應,爲了避免接收到不正確的數據,就使用確認號機制,來明確我們需要的是什麼數據

接着是控制位,一共有6種,其各自含義如下:

  • URG:緊急指針有效
  • ACK:確認有效
  • PSH:請求急迫
  • RST:連接復位
  • SYN:同步序列號
  • FIN:終止連接

在這6種中,我們用到的至少有3種:ACK、SYN,還有FIN,ACK表示分組是一個ACK響應分組,SYN表示分組是一個請求連接分組,FIN表示分組是一個終止連接分組。我們要注意的是,這些控制位之間並不一定是互斥的,比如ACK可以和SYN同時有效,一個分組中並不是只能存在一個控制位

4. 連接的建立過程

即使完全不瞭解TCP協議,也應該聽說過“三次握手與四次揮手”的概念,三次握手指的就是連接創建的過程,四次揮手就是連接拆除的過程

我們先來看“三次握手”的過程,首先來看圖示:三次握手
我們把主動建立連接的一方叫做客戶端,另一方叫做服務端,整個過程可以簡要表述爲以下這樣:

  • 背景:客戶端和服務端均處於CLOSED狀態,等待進一步操作
  • 準備:服務端進入LISITEN狀態,監聽客戶端建立連接的請求
  • 第一次握手:客戶端服務端發起建立連接的請求(SYN分組),自身進入SYN-SENT狀態
  • 第二次握手:服務端接收到客戶端的請求,給客戶端發送確認消息(SYN + ACK分組),自身進入SYN-RCVD狀態
  • 第三次握手:客戶端服務端發送最終確認消息(ACK分組),自身進入ESTABLISHED狀態,可以進行數據傳送
  • 連接建立:服務端接受到客戶端的確認消息,自身也進入ESTABLISHED狀態,可以進行數據傳送

我們可以在圖中看到,每一次握手消息的發送都攜帶了seq,即序列號,這裏需要明確一點,前兩次握手消息均不攜帶任何數據,但是仍佔用一個序列號,最終傳送數據的序列號就是基於握手消息的序列號(連接的一方發送的分組序列號是連續的,每次加1)

我們剛纔指說道前兩次握手消息不攜帶數據,並佔用一個序列號,但是我們並沒有提到第三次握手,是因爲第三次握手是一個純ACK消息,也就是單純地進行響應(對服務端響應的消息進行響應),所以這個報文如果不攜帶數據,則不佔用序列號(不佔用序列號的意思是不使用新的序列號,而不是不攜帶序列號)

這裏還有一種很少見的情況,就是雙方同時建立連接,則彼此都發送SYN+ACK段,直接建立連接,但是這種情況畢竟很少,所以不再進行進一步的討論

雖然連接創建的過程已經講完了,但是可能很多人疑惑爲什麼創建的過程要設計的這麼麻煩,這裏我們從正反兩方面來解釋:

爲什麼不使用一次握手或者兩次握手?

首先我們要知道,定義TCP協議的人肯定希望連接的創建過程儘量簡化,現在三次握手已經能解決問題了,所以我們這裏就不討論爲什麼不使用四次握手或者五次握手

那爲什麼不使用一次握手?其實一次握手和兩次握手遇到的問題都是一樣的,所以我們放在一起說。一次握手和兩次握手都有一個共同的特點,那就是接收到第一次握手的一方會立刻建立一條TCP連接通路,這就帶來了一個問題。因爲數據通信的理論之一是網絡鏈路的不可靠性,所以我們不能以發送的數據包都能被接收作爲協議的前提。TCP協議制訂了一個超時重傳的機制,如果發送的分組一定時間內沒有響應,就會觸發超時重傳,進行分組的再次發送

我們再談回一/兩次握手,如果發送的第一次握手消息沒有在指定時間內響應,客戶端就會重新發送一份握手消息,現在我們想一下,如果連接被拆除後,因爲網絡原因第一次發送的分組才慢悠悠地傳到服務端,則會導致創建一個新的TCP通路,顯然這種情況會導致資源被浪費

如果用更正式的說法就是,爲了防止已失效的連接請求報文段重新突然發送到服務端,導致建立不必要的連接,所以一次握手和兩次握手均是不可行的,爲了解決這種情況要麼使用三次握手,要麼就只能消耗更多的資源來進行額外的判斷

爲什麼要使用三次握手

三次握手協議是有其本身的意義的,每一次握手消息都不是多餘的。TCP連接創建的前提就是確認連接雙方都能夠正常的接收和發送消息,因爲不存在第三方確認機構,所以只能由連接雙方自行判斷,每一次握手消息都是在進行詢問與判斷:

  • 服務端接收到第一次握手消息:服務端確認了客戶端可以正常發送消息(發送了握手消息)
  • 客戶端接收到第二次握手消息:客戶端確認了服務端可以正常接收消息(接收了自己發送的握手消息),也可以正常發送消息(發送了握手消息)
  • 服務端接收到第三次握手消息:服務端確認了客戶端可以正常接收消息(接受了自己發送的握手消息)

這樣,經過至少三次握手之後,連接的一方纔能夠對另一方接收和發送消息的能力有一個準確的判斷,我個人認爲這種解釋相比於反證的方式要更爲清晰

5. 連接的拆除

連接的拆除,或者叫連接的釋放,是一個四次揮手的過程,我們同樣來看圖:四次揮手
我們把主動提出關閉請求的一方叫做客戶端,另一方叫做服務端(實際上一般情況下都是由客戶端發起主動關閉),整個過程可以分爲以下步驟:

  • 背景:客戶端和服務端都處於ESTABISHED狀態,等待進一步操作
  • 第一次揮手:客戶端服務端發送連接拆除的請求(FIN分組),自身進入FIN-WAIT-1狀態
  • 第二次揮手:服務端收到客戶端的請求,單向關閉客戶端的連接,向客戶端發送響應消息(ACK分組),自身進入CLOSE-WAIT狀態
  • 單向數據傳送:客戶端接收到服務端的響應,不再發送數據(但是可以進行響應),僅由服務端發送一些剩餘的數據,進入FIN-WAIT-2狀態
  • 第三次揮手:服務端的剩餘消息發送完畢,向客戶端發送連接拆除請求(FIN + ACK分組),自身進入LAST-ACK狀態
  • 第四次揮手:客戶端收到服務端的請求,向服務端發送響應消息(ACK分組),自身進入TIME-WAIT狀態
  • 服務端連接斷開:服務端收到客戶端的響應消息,斷開連接,自身進入CLOSED狀態
  • 客戶端連接斷開:客戶端等待一段時間後,自行斷開連接,自身進入CLOSED狀態

有了三次握手的基礎,相信四次揮手的過程理解起來會容易很多。揮手信息中發送的序列號和確認號我就不再進一步講解了,圖中已經很詳細了,這裏就提兩點:

  • 即使FIN分組中不攜帶任何數據,也要佔用一個序列號
  • 如果FIN + ACK分組中不攜帶任何數據,則僅佔用一個序列號
  • 第四次揮手的ACK分組不攜帶數據,也不佔用序列號

四次揮手的過程與三次握手大同小異,我們這裏只針對最重要的兩個點進行討論

爲什麼要使用四次揮手?

講三次握手的地方已經證明了一次和兩次握手的不可行性,但是我們很容易就能想到,爲什麼不採用三次揮手?其實三次揮手是可行的,但是在TCP中,一端停止發送數據後,仍可以繼續接收數據,也就是半關閉的特性。爲了這一特性,所以在揮手過程中插入了一個單向數據傳送的過程,使得服務端可以在客戶端主動關閉後繼續發送數據

TIME-WAIT狀態是什麼

TIME-WAIT狀態的字面意義是等待時間,和三次握手不一樣,客戶端發送最後的ACK段之後並沒有直接進入CLOSED狀態,而是等待一段時間後再自行關閉,這一段時間默認爲2MSL(Maximum Segment Lifetime,即分組最長存活時間),可以在系統配置文件中手動更改。如果在TIME-WAIT期間收到了服務端的消息,依然可以進行響應

我們以客戶端發送的最後一次ACK段爲起點分析,如果這個ACK由於網絡問題暫時沒有被服務端接收到,服務端的定時器到期,會進行FIN段的重傳,假設我們沒有TIME-WAIT狀態,而是在發送ACK後立刻進入CLOSED狀態,資源都已經被銷燬了,客戶端無法對服務端的消息作出響應,這就導致服務端一方得不到對FIN的響應消息,造成了連接的異常

TIME-WAIT的存在理由知道了,那麼爲什麼要設定爲2MSL呢?剛纔我們提到了,MSL指的就是一個分組在網絡中存活的最大時間,換句話來說,如果一個分組經過了MSL還沒有傳送到目的方,那麼完全就能夠認爲這個分組在網絡中丟失,不可能再次被目的方接收。一個分組一來一回最多爲2MSL,也就是說如果2MSL之後沒有任何消息發送過來,就能夠確信對方已經對自己的消息做出了迴應

在實際生產中,如果發現系統存在大量的TIME-WAIT狀態,可以適當調小這個值

補充

最後再提一個保活時間,並不是很重要的一個點,感興趣的可以自行了解,其意義是當客戶端故障後,服務端在保活時間後會發送探測消息,來決定是否釋放連接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章