傳輸控制協議(TCP,Transmission Control Protocol)是一種面向連線的、可靠的、基於位元組流的傳輸層通訊協議,由 IETF 的 RFC 793 定義,是為了在不可靠的網際網路絡上提供可靠的端到端位元組流而專門設計的一個傳輸協議。
網際網路絡與單個網路有很大的不同,因為網際網路絡的不同部分可能有截然不同的拓撲結構、頻寬、延遲、資料包大小和其他引數。TCP 的設計目標是能夠動態地適應網際網路絡的這些特性,而且具備面對各種故障時的健壯性。
應用層向 TCP 層傳送用於網間傳輸的、用 8 位位元組表示的資料流,然後 TCP 把資料流分割槽成適當長度的報文段(通常受該計算機連線的網路的資料鏈路層的最大傳輸單元(MTU)的限制)。之後 TCP 把結果包傳給 IP 層,由它來通過網路將包傳送給接收端實體的 TCP 層。TCP 為了保證不發生丟包,就給每個包一個序號,同時序號也保證了傳送到接收端實體的包的按序接收。然後接收端實體對已成功收到的包發回一個相應的確認(ACK);如果傳送端實體在合理的往返時延(RTT)內未收到確認,那麼對應的資料包就被假設為已丟失將會被進行重傳。TCP 用一個校驗和函式來檢驗資料是否有錯誤;在傳送和接收時都要計算校驗和。
TCP 訊息頭
下面對每個欄位含義進行解釋
-
源、目標埠號欄位:佔 16 位元 (2 位元組)。TCP 協議通過使用”埠”來標識源端和目標端的應用程式。埠號可以使用0到65535之間的任何數字。在收到服務請求時,作業系統動態地為客戶端的應用程式分配埠號。在伺服器端,每種服務在”眾所周知的埠”(Well-Know Port)為使用者提供服務。
-
順序號欄位:佔 32 位元。用來標識從 TCP 源端向 TCP 目標端傳送的資料位元組流,它表示在這個報文段中的第一個資料位元組。在TCP傳送的流中,每一個位元組一個順序號。e.g.如果一個 TCP 報文段的序號為 301,它攜帶了 100 位元組的資料,就表示這 100 個位元組的資料的位元組序號範圍是 [301, 400],該報文段攜帶的第一個位元組序號是 301,最後一個位元組序號是 400。所以順序號號確保了TCP傳輸的有序性。
-
確認號欄位:佔 32 位元。只有 ACK 標誌為1時,確認號欄位才有效。它包含目標端所期望收到源端的下一個資料位元組。比如建立連線時,SYN 報文的 ACK 標誌位為 0。
-
頭部長度欄位:佔 4 位元。給出頭部佔 32 位元的數目。沒有任何選項欄位的 TCP 頭部長度為 20 位元組;最多可以有 60 位元組的 TCP 頭部。4位首部長度欄位所能表示的最大值為1111,轉化為10進製為15,15*32/8 = 60,故報頭最大長度為60位元組
-
標誌位欄位(U、A、P、R、S、F):佔 6 位元。各位元的含義如下:視窗大小欄位:佔 16 位元。此欄位用來進行流量控制。單位為位元組數,這個值是本機期望一次接收的位元組數。
-
-
URG:緊急指標標誌,為 1 時表示緊急指標有效,為 0 則忽略緊急指標。
-
ACK:確認序號標誌,為 1 時表示確認號有效,為 0 表示報文中不含確認資訊,忽略確認號欄位。
-
PSH:push 標誌,為 1 表示是帶有 push 標誌的資料,指示接收方在接收到該報文段以後,應儘快將這個報文段交給應用程式,而不是在緩衝區排隊。
-
RST:重置連線標誌,用於重置由於主機崩潰或其他原因而出現錯誤的連線。或者用於拒絕非法的報文段和拒絕連線請求。
-
SYN:同步序號,用於建立連線過程,在連線請求中,SYN = 1 和 ACK = 0 表示該資料段沒有使用捎帶的確認域,而連線應答捎帶一個確認,即 SYN = 1 和 ACK = 1。
-
FIN:finish 標誌,用於釋放連線,為1時表示傳送方已經沒有資料傳送了,即關閉本方資料流。
-
-
TCP 校驗和欄位:佔 16 位元。對整個 TCP 報文段,即 TCP 頭部和 TCP 資料進行校驗和計算,並由目標端進行驗證。
- 緊急指標欄位:佔 16 位元。它是一個偏移量,和序號欄位中的值相加表示緊急資料最後一個位元組的序號。
- 選項和填充:最常見的可選欄位是最長報文大小,又稱為 MSS(Maximum Segment Size),每個連線方通常都在通訊的第一個報文段(為建立連線而設定SYN標誌為1的那個段)中指明這個選項,它表示本端所能接受的最大報文段的長度。選項長度不一定是32位的整數倍,所以要加填充位,即在這個欄位中加入額外的零,以保證TCP頭是32的整數倍。
- 資料部分: TCP 報文段中的資料部分是可選的。在一個連線建立和一個連線終止時,雙方交換的報文段僅有 TCP 首部。如果一方沒有資料要傳送,也使用沒有任何資料的首部來確認收到的資料。在處理超時的許多情況中,也會傳送不帶任何資料的報文段。
TCP 三次握手
TCP是一個面向連線的協議,無論哪一方向另一方傳送資料之前,都必須先在雙方之間建立一條連線,建立一條連線有以下過程。
-
第一次握手:建立連線時,客戶端傳送 syn 包(syn=x)到伺服器,並進入SYN_SENT狀態,等待伺服器確認;SYN:同步序列編號(Synchronize Sequence Numbers)。
-
第二次握手:伺服器收到 syn 包,必須確認客戶的 SYN(ack=x+1),同時自己也傳送一個 SYN 包(syn=y),即 SYN+ACK 包,此時伺服器進入 SYN_RECV 狀態;
-
第三次握手:客戶端收到伺服器的 SYN + ACK 包,向伺服器傳送確認包 ACK(ack=y+1),此包傳送完畢,客戶端和伺服器進入 ESTABLISHED(TCP連線成功)狀態,完成三次握手。
這三個報文段完成連線的建立,這個過程成為三次握手。
SYN 攻擊
在三次握手過程中,Server 傳送 SYN-ACK 之後,收到 Client 的 ACK 之前的 TCP 連線稱為半連線(half-open connect),此時 Server 處於 SYN_RCVD 狀態,當收到 ACK 後,Server 轉入 ESTABLISHED 狀態。
SYN 攻擊就是 Client 在短時間內偽造大量不存在的 IP 地址,並向 Server 不斷地傳送 SYN 包,Server 回覆確認包,並等待 Client 的確認,由於源地址是不存在的,因此,Server 需要不斷重發直至超時,這些偽造的 SYN 包將長時間佔用未連線佇列,導致正常的 SYN 請求因為佇列滿而被丟棄,從而引起網路堵塞甚至系統癱瘓。
SYN 攻擊時一種典型的 DDOS 攻擊,檢測 SYN 攻擊的方式非常簡單,即當 Server 上有大量半連線狀態且源 IP 地址是隨機的,則可以斷定遭到 SYN 攻擊了。
解決方案:
-
1、無效連線監視釋放
-
2、延緩TCB分配方法
當開放了一個 TCP 埠後,該埠就處於 Listening 狀態,不停地監視發到該埠的 Syn 報文,
一旦接收到 Client 發來的 Syn 報文,就需要為該請求分配一個 TCB(Transmission Control Block),並返回一個 SYN ACK 命令,立即轉為 SYN-RECEIVED 即半開連線狀態。
通常一個 TCB 至少需要 280 個位元組,在某些作業系統中 TCB 甚至需要 1300 個位元組,而某些作業系統在 SOCK 的實現上最多可開啟 512 個半開連線。
消耗伺服器資源主要是因為當 SYN 資料包文一到達,系統立即分配 TCB,從而佔用了資源。
解決:
收到 SYN 資料包文時不急於去分配TCB,而是先回應一個SYN ACK報文,並在一個專用HASH表(Cache)中儲存這種半開連線資訊,直到收到正確的回應ACK報文再分配TCB。
TCP終止連線過程(四次揮手)
-
客戶端程式發出連線釋放報文,並且停止傳送資料。釋放資料包文首部,FIN=1,其序列號為 seq=u(等於前面已經傳送過來的資料的最後一個位元組的序號加 1),此時,客戶端進入 FIN-WAIT-1(終止等待1)狀態。 TCP 規定,FIN報文段即使不攜帶資料,也要消耗一個序號。
-
伺服器收到連線釋放報文,發出確認報文,ACK=1,ack=u+1,並且帶上自己的序列號 seq=v,此時,服務端就進入了CLOSE-WAIT(關閉等待)狀態。TCP伺服器通知高層的應用程式,客戶端向伺服器的方向就釋放了,這時候處於半關閉狀態,即客戶端已經沒有資料要傳送了,但是伺服器若傳送資料,客戶端依然要接受。這個狀態還要持續一段時間,也就是整個 CLOSE-WAIT 狀態持續的時間。
-
客戶端收到伺服器的確認請求後,此時,客戶端就進入 FIN-WAIT-2(終止等待2)狀態,等待伺服器傳送連線釋放報文(在這之前還需要接受伺服器傳送的最後的資料)。
-
伺服器將最後的資料傳送完畢後,就向客戶端傳送連線釋放報文,FIN=1,ack=u+1,由於在半關閉狀態,伺服器很可能又傳送了一些資料,假定此時的序列號為 seq=w,此時,伺服器就進入了 LAST-ACK(最後確認)狀態,等待客戶端的確認。
-
客戶端收到伺服器的連線釋放報文後,必須發出確認,ACK=1,ack=w+1,而自己的序列號是 seq=u+1,此時,客戶端就進入了 TIME-WAIT(時間等待)狀態。注意此時 TCP連線還沒有釋放,必須經過 2∗∗MSL(最長報文段壽命)的時間後,當客戶端撤銷相應的TCB後,才進入 CLOSED 狀態。
-
伺服器只要收到了客戶端發出的確認,立即進入 CLOSED 狀態。同樣,撤銷 TCB 後,就結束了這次的 TCP 連線。可以看到,伺服器結束 TCP 連線的時間要比客戶端早一些。
TCP 狀態機
上圖是 TCP 的狀態機。
-
CLOSED:狀態時初始狀態。
-
LISTEN:被動開啟,伺服器端的 狀態變為 LISTEN (監聽)。被動開啟的概念:連線的一端的應用程式通知作業系統,希望建立一個傳入的連線。這時候作業系統為連線的這一端建立一個連線。與之對應的是主動連線:應用程式通過主動開啟請求來告訴作業系統建立一個連線。
-
SYNRECVD:伺服器端收到 SYN 後,狀態為 SYN;傳送 SYN ACK;
-
SYN_SENTY:應用程式傳送 SYN 後,狀態為 SYN_SENT;
-
ESTABLISHED:SYNRECVD 收到 ACK 後,狀態為 ESTABLISHED; SYN_SENT 在收到 SYN ACK,傳送 ACK,狀態為 ESTABLISHED;
-
CLOSE_WAIT:伺服器端在收到 FIN 後,傳送 ACK,狀態為 CLOSE_WAIT;如果此時伺服器端還有資料需要傳送,那麼就傳送,直到資料傳送完畢;此時,伺服器端傳送FIN,狀態變為 LAST_ACK;
-
FIN_WAIT_1:應用程式端傳送 FIN,準備斷開 TCP 連線;狀態從 ESTABLISHED -> FIN_WAIT_1;
-
FIN_WAIT_2:應用程式端只收到伺服器端得 ACK 訊號,並沒有收到FIN訊號;說明伺服器端還有資料傳輸,那麼此時為半連線;
-
TIME_WAIT:有兩種方式進入該狀態:
-
FIN_WAIT_1進入:此時應用程式埠收到 FIN+ACK(而不是像 FIN_WAIT_2 那樣只收到 ACK,說明資料已經傳送完畢)並向伺服器埠傳送 ACK;
-
FIN_WAIT_2進入:此時應用程式埠收到了 FIN,然後向伺服器端傳送 ACK;TIME_WAIT 是為了實現 TCP 全雙工連線的可靠性關閉,用來重發可能丟失的 ACK 報文;需要持續 2 個 MSL (最大報文生存時間):假設應用程式埠在進入 TIME_WAIT後,2 個 MSL 時間內並沒有收到FIN,說明應用程式最後發出的 ACK 已經收到了;否則,會在 2 個 MSL 內在此收到ACK報文;
-
客戶端應用程式的狀態遷移圖
客戶端的狀態可以用如下的流程來表示:
CLOSED -> SYN_SENT -> ESTABLISHED -> FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT -> CLOSED
以上流程是在程式正常的情況下應該有的流程,從書中的圖中可以看到,在建立連線時,當客戶端收到 SYN 報文的 ACK 以後,客戶端就開啟了資料互動地連線。而結束連線則通常是客戶端主動結束的,客戶端結束應用程式以後,需要經歷 FIN_WAIT_1,FIN_WAIT_2 等狀態,這些狀態的遷移就是前面提到的結束連線的四次握手。
伺服器的狀態遷移圖
伺服器的狀態可以用如下的流程來表示:
CLOSED -> LISTEN -> SYN 收到 -> ESTABLISHED -> CLOSE_WAIT -> LAST_ACK -> CLOSED
在建立連線的時候,伺服器端是在第三次握手之後才進入資料互動狀態,而關閉連線則是在關閉連線的第二次握手以後(注意不是第四次)。而關閉以後還要等待客戶端給出最後的ACK 包才能進入初始的狀態。
其他狀態遷移
書中的圖還有一些其他的狀態遷移,這些狀態遷移針對伺服器和客戶端兩方面的總結如下
-
LISTEN -> SYN_SENT,對於這個解釋就很簡單了,伺服器有時候也要開啟連線的嘛。
-
SYN_SENT -> SYN 收到,伺服器和客戶端在 SYN_SENT 狀態下如果收到 SYN 資料包,則都需要傳送 SYN 的 ACK 資料包並把自己的狀態調整到 SYN 收到狀態,準備進入ESTABLISHED
-
SYN_SENT -> CLOSED,在傳送超時的情況下,會返回到 CLOSED 狀態。
-
SYN_收到 -> LISTEN,如果受到 RST 包,會返回到 LISTEN 狀態。
-
SYN_收到 -> FIN_WAIT_1,這個遷移是說,可以不用到 ESTABLISHED 狀態,而可以直接跳轉到 FIN_WAIT_1 狀態並等待關閉。
2 MSL 等待狀態
書中給的圖裡面,有一個 TIME_WAIT 等待狀態,這個狀態又叫做 2 MSL 狀態,說的是在 TIME_WAIT2 傳送了最後一個 ACK 資料包以後,要進入 TIME_WAIT 狀態,這個狀態是防止最後一次握手的資料包沒有傳送到對方那裡而準備的(注意這不是四次握手,這是第四次握手的保險狀態)。這個狀態在很大程度上保證了雙方都可以正常結束,但是,問題也來了。
由於插口的 2MSL 狀態(插口是IP和埠對的意思,socket),使得應用程式在 2 MSL 時間內是無法再次使用同一個插口的,對於客戶程式還好一些,但是對於服務程式,例如httpd,它總是要使用同一個埠來進行服務,而在 2 MSL 時間內,啟動 httpd 就會出現錯誤(插口被使用)。為了避免這個錯誤,伺服器給出了一個平靜時間的概念,這是說在 2 MSL 時間內,雖然可以重新啟動伺服器,但是這個伺服器還是要平靜的等待 2 MSL 時間的過去才能進行下一次連線。
FIN_WAIT_2 狀態
這就是著名的半關閉的狀態了,這是在關閉連線時,客戶端和伺服器兩次握手之後的狀態。在這個狀態下,應用程式還有接受資料的能力,但是已經無法傳送資料,但是也有一種可能是,客戶端一直處於 FIN_WAIT_2 狀態,而伺服器則一直處於 WAIT_CLOSE 狀態,而直到應用層來決定關閉這個狀態。
RST,同時開啟和同時關閉
RST 是另一種關閉連線的方式,應用程式應該可以判斷 RST 包的真實性,即是否為異常中止。而同時開啟和同時關閉則是兩種特殊的 TCP 狀態,發生的概率很小。
常見面試題
【問題1】為什麼連線的時候是三次握手,關閉的時候卻是四次握手?
答:因為當 Server 端收到 Client 端的 SYN 連線請求報文後,可以直接傳送 SYN+ACK 報文。其中 ACK 報文是用來應答的,SYN 報文是用來同步的。但是關閉連線時,當 Server 端收到 FIN 報文時,很可能並不會立即關閉 SOCKET,所以只能先回復一個 ACK 報文,告訴 Client 端,"你發的 FIN 報文我收到了"。只有等到我 Server 端所有的報文都傳送完了,我才能傳送 FIN 報文,因此不能一起傳送。故需要四步握手。
【問題2】為什麼TIME_WAIT狀態需要經過2MSL(最大報文段生存時間)才能返回到CLOSE狀態?
雖然按道理,四個報文都傳送完畢,我們可以直接進入 CLOSE 狀態了,但是我們必須假象網路是不可靠的,有可以最後一個 ACK 丟失。所以TIME_WAIT狀態就是用來重發可能丟失的 ACK 報文。在 Client 傳送出最後的 ACK 回覆,但該 ACK 可能丟失。Server 如果沒有收到 ACK,將不斷重複傳送 FIN 片段。所以 Client 不能立即關閉,它必須確認 Server 接收到了該ACK。Client 會在傳送出ACK 之後進入到 TIME_WAIT 狀態。Client 會設定一個計時器,等待 2MSL 的時間。如果在該時間內再次收到FIN,那麼 Client 會重發 ACK 並再次等待2 MSL。所謂的 2 MSL 是兩倍的 MSL(Maximum Segment Lifetime)。MSL 指一個片段在網路中最大的存活時間,2MSL 就是一個傳送和一個回覆所需的最大時間。如果直到 2 MSL,Client 都沒有再次收到 FIN,那麼 Client 推斷 ACK 已經被成功接收,則結束 TCP 連線。
【問題3】為什麼不能用兩次握手進行連線?
3 次握手完成兩個重要的功能,既要雙方做好傳送資料的準備工作(雙方都知道彼此已準備好),也要允許雙方就初始序列號進行協商,這個序列號在握手過程中被髮送和確認。
現在把三次握手改成僅需要兩次握手,死鎖是可能發生的。作為例子,考慮計算機 S 和 C 之間的通訊,假定 C 給 S 傳送一個連線請求分組,S 收到了這個分組,併發 送了確認應答分組。按照兩次握手的協定,S 認為連線已經成功地建立了,可以開始傳送資料分組。可是,C 在 S 的應答分組在傳輸中被丟失的情況下,將不知道 S 是否已準備好,不知道 S 建立什麼樣的序列號,C 甚至懷疑 S 是否收到自己的連線請求分組。在這種情況下,C 認為連線還未建立成功,將忽略 S 發來的任何資料分組,只等待連線確認應答分組。而 S 在發出的分組超時後,重複傳送同樣的分組。這樣就形成了死鎖。
【問題4】如果已經建立了連線,但是客戶端突然出現故障了怎麼辦?
TCP 還設有一個保活計時器,顯然,客戶端如果出現故障,伺服器不能一直等下去,白白浪費資源。伺服器每收到一次客戶端的請求後都會重新復位這個計時器,時間通常是設定為 2 小時,若兩小時還沒有收到客戶端的任何資料,伺服器就會傳送一個探測報文段,以後每隔 75 秒鐘傳送一次。若一連傳送 10 個探測報文仍然沒反應,伺服器就認為客戶端出了故障,接著就關閉連線
參考文章
https://blog.csdn.net/whuslei/article/details/6667471
https://blog.csdn.net/qq_38950316/article/details/81087809
https://www.sohu.com/a/321838267_495675
https://www.iteye.com/blog/uule-2213562