TCP 協議詳細解析

TCP 是 TCP/IP 協議族中一個最核心的協議,它向下使用網絡層 IP 協議,向上爲應用層 HTTP、FTP、SMTP、POP3、SSH、Telnet 等協議提供支持。本文給出 TCP 報文格式的詳細說明,介紹網絡數據包傳遞中如何進行地址解析、建立 TCP 連接的三次握手過程以及斷開 TCP 連接的四次揮手過程。

1.  簡介

傳輸控制協議(英語:Transmission Control Protocol,縮寫:TCP)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議,由國際互聯網工程任務組 (The Internet Engineering Task Force, IETF) 的 RFC793 定義。在簡化的計算機網絡 OSI 模型中,它完成傳輸層所指定的功能。

在 TCP 定義中,有以下 3 點需要特別說明:

(1) 什麼是面向連接?

面向連接是相對於另一個傳輸層協議 UDP(User Datagram Protocol, 用戶數據報協議) 而言的。TCP 在開始傳輸數據前要先經歷三次握手建立連接,並通過連接一對一發送消息,傳輸結束後通過四次揮手斷開連接。

而 UDP 是無連接的,發送方在發送數據之前不需要與接收方建立連接,即刻可以傳輸數據,每個 UDP 數據包都是獨立的,相互之間沒有關聯,因此 UDP 可以一對一、一對多或多對多發送消息。

(2) 什麼是可靠的通信協議?

是否可靠也是相對於 UDP 而言的。TCP 自身有三次握手和超時重傳等機制確保數據的可靠傳輸,發送方在發送數據包後會等待接收方發送確認 (ACK) 消息。如果發送方在一定時間內未收到確認消息,它將假定數據丟失,並重新發送數據。接收方收到重複的數據包時會發送冗餘的 ACK 消息來通知發送方,以避免數據丟失。同時 TCP 還提供流量控制和擁塞控制,以保持網絡的穩定性和性能。因此無論網絡如何變化,只要不是主機宕機等原因都可以保證一個報文可以到達目標主機。

相對於 TCP 的可靠傳輸,UDP 是不可靠的。UDP 數據包的傳輸過程中不提供確認、重傳、流量控制和擁塞控制等機制,因此 UDP 數據包可能丟失、重複、亂序或損壞。

(3) 什麼是面向字節流的?

TCP 是面向字節流的傳輸,雖然應用程序和 TCP 的交互是一次一個數據塊 (大小不等),但 TCP 把應用程序看成是一連串的無結構的字節流。TCP 有一個緩衝,當應用程序傳送的數據塊太長,TCP 就可以把它劃分短一些再傳送。如果應用程序一次只發送一個字節,TCP 也可以等待積累有足夠多的字節後再構成報文段發送出去。

與面向字節流相對的是 UDP 的面向報文。UDP 對應用層交下來的報文,既不合並也不拆分,而是保留這些報文的邊界,即應用層交給 UDP 多長的報文,UDP 就照樣發送,一次發送一個報文。因此,應用程序必須選擇合適大小的報文。若報文太長,則 IP 層需要分片,降低效率。若太短,會使 IP 報文太小。

2.TCP 報文格式

瞭解報文格式是搞懂一個通信協議的必經之路。TCP 報文由 TCP 首部 (報頭) 和應用數據構成,其中 TCP 首部是 TCP 協議的核心所在,應用數據部分是 TCP 報文的負載,如下圖所示。

以下詳細介紹各字段含義:

3. 數據包傳遞的地址解析

我們在 “IP 協議詳細解析” 一文中介紹了 IP 報頭中 “源地址” 和 “目的地址”,與本文 TCP 報頭中的 “源端口” 和 “目的端口” 共同確定了數據包傳遞過程中需要的地址,如下圖所示。

類比日常工作中郵寄信件,我們裝在信封裏的信件相當於要傳遞的數據,標準的信件格式是要在信封上寫 “收信人地址” 和 “寄信人地址”,相當於 IP 地址,其中,“收信人地址” 對應數據包裏 IP 報頭中的 “目的 IP 地址”,“寄信人地址” 對應數據包裏 IP 報頭中的 “源 IP 地址”,寫上寄信、收信兩個地址就可以保證信件可以郵寄到目的地了。

但信件郵寄到目的地址後由誰來收?從上面這封信的收件人地址檢索到這個地址是位於上海市浦東新區張江 “A 公司 B 部門” 的,這個部門可能有成百上千人,收件人不明確,即使把信件送到這個地址,也沒辦法投遞到具體的收信人。

因此,郵件信件需要填寫 “收件人姓名”、“收件人地址” 和“寄件人姓名”、“寄件人地址”的組合,才能保證信件能準確投遞到具體的收件人手中。這裏的收信人姓名相當於 TCP 報頭的目的端口,寄信人姓名相當於 TCP 報頭的源端口。

對比傳遞信件,我們來看網絡數據包傳遞過程的例子。位於北京的李四 (電腦 IP 地址: 106.54.28.25) 給上海的張三 (電腦 IP 地址: 114.92.67.193) 通過 QQ(端口: 80) 發送一條消息,如下圖所示:

首先,李四電腦將消息打包成 TCP 數據報後,添加 IP 報頭和以太網報頭形成網絡數據包,發送到計算機網絡中。計算機網絡通過數據包中 IP 報頭的目的 IP 地址 (114.92.67.193) 把該數據包準確傳遞到張三電腦。

張三電腦收到了李四電腦發送過來的數據包後,由於張三電腦上同時運行有多個程序 (例如圖中的 QQ、微信、Foxmail 等),雖然張三電腦知道這個數據包是傳輸給它的,但是它不知道該把這個數據包中的數據交給哪個程序。

針對這個問題,使用數據包中 TCP 報頭的源端口和目的端口,根據不同的程序使用不同端口號來確定應用程序併發送和接受數據,這樣數據包就能像郵寄信件一樣準確投遞到具體電腦上指定的程序了。例如我們指定張三電腦上 QQ、微信、Foxmail 使用的端口分別是 80、8900 和 110,那麼當收到數據包裏目的端口 80 就是傳輸給 QQ 的。

上述例子還可以引申出數據包結構中的其他字段的作用,例如我們收到信後可以簡單地通過信封是否完整,來檢查該信件是否被別人在傳輸途中拆開並篡改過信件內容。對於網絡數據包,TCP 報頭的 “校驗和”(Checksum) 可以驗證收到數據包數據是否在途被別人拆開修改過。

4.TCP 連接

爲什麼需要建立 TCP 連接?首先,IP 協議是無連接的,IP 並不維護任何關於後續數據報的狀態信息,每個數據報的處理相互獨立。這種無連接的優點是不佔用線路,降低了對網絡線路的要求;此外,IP 協議是不可靠的,不能保證 IP 數據報能成功到達目的地,是一種盡力而爲的傳輸服務,路由器對 IP 報文出現錯誤的處理方式是丟包,併發送 ICMP(Internet Control Message Protocol,互聯網控制協議) 控制消息給源地址。因爲 IP 協議是無連接、不可靠的,因此,需要上層 TCP 來建立連接和差錯重傳,實現面向連接的、可靠的、基於字節流的傳輸層通信協議。

4.1 三次握手過程詳解

由於建立 TCP 連接的過程需要來回 3 次,所以將這個過程形象的叫做三次握手 (Three-Way Handshake),一旦建立連接,兩臺主機就可以進行全雙工的通信。

下面是三次握手的詳細過程,包括髮送的報文段內容:

(1) 第一次握手

首先客戶端發起連接請求,向服務器發送一個 SYN(同步) 報文段,段中包含了目的端口和本機端口,設置 SYN 標誌位爲 1,即 SYN=1,並設置序號字段 (Sequence Number) 爲一個隨機選擇的 x,即 seq=x,也就是初始序號 (Initial Sequence Number, ISN),如果是第一個連接,很可能是 0。此時服務器對應的端口要處於監聽狀態,客戶端發起請求後進入 SYN_SENT 狀態,等待服務器的確認。

(2) 第二次握手

服務端收到客戶端發來的 SYN 報文段,對這個 SYN 報文段進行確認。服務器向客戶端發送一個 SYN-ACK 報文段作爲迴應,報文段中的標誌位設置爲 SYN=1ACK=1,表示同時作爲確認和同步;序號字段設置爲服務器的隨機選擇的初始序號 y(服務端的 TCP 段序號),即 seq=y;確認號字段 (Acknowledgment Number) 設置爲客戶端的初始序號加 1,即 ack=x+1。服務器端將上述所有信息放到一個 TCP 段 (即 SYN+ACK 段) 中,一併發送給客戶端,此時服務器進入 SYN_RECV 狀態。

(3) 第三次握手

客戶端接收到服務端發來的 SYN+ACK 報文段後,要向服務端發送一個 ACK(確認) 報文段,對連接請求的確認進行確認。報文段中的標誌位設置爲 ACK=1,確認號字段設置爲服務器的初始序號加 1,即 ack=y+1,序號字段設置爲客戶端的初始序號加 1,即 seq=x+1。此時客戶端進入 ESTABLISHED(已連接) 狀態,服務端接收到此 TCP 段,也將進入 ESTABLISHED 狀態,也就標誌着三次握手結束,連接成功建立。

三次握手完成之後,TCP 連接就正式建立起來了,雙方可以開始進行數據的可靠傳輸。三次握手的目的是確保雙方的初始序號和確認號的同步,並驗證雙方的可達性。通過這個過程,TCP 可以建立一個可靠的雙向通信通道,在後續的數據傳輸中保證數據的可靠性和順序性。

4.2 四次揮手

四次揮手是 TCP 斷開連接的過程。

(1) 第一次揮手

客戶端數據發送完成,則向服務端發送連接釋放請求的 FIN 報文 (請求連接終止:FIN=1),主動關閉 TCP 連接。報文中會指定一個序列號 seq=u,並停止再發送數據,但依然能夠接收數據。此時客戶端處於 FIN_WAIT_1 狀態,等待服務端確認。TCP 規定,FIN 報文即使不攜帶數據,也要消耗一個序號。

(2) 第二次揮手

服務端收到 FIN 報文之後,通知相應的高層應用進程,告訴它客戶端向服務端這個方向的連接已經釋放了。此時服務端向客戶端發出連接釋放的應答 ACK 報文,並進入了 CLOSE_WAIT(關閉等待) 狀態。ACK 報文頭包含:ACK=1ack=u+1,並且帶上自己的序列號 seq=v。這裏 ack=u+1 是第一次揮手的序列值 + 1,表示希望收到從第 u+1 個字節開始的報文段,並且已經成功接收了前 u 個字節。

客戶端收到服務端的確認後,進入 FIN_WAIT_2 狀態,等待服務端發出的連接釋放報文段。

前兩次揮手既讓服務端知道了客戶端想釋放連接,也讓客戶端知道了服務端已瞭解自己想要釋放連接的請求。

(3) 第三次揮手

如果服務端也想斷開連接,就向客戶端發送連接釋放報文。由於在 CLOS_WAIT 狀態,服務端很可能又發送了一些數據,假定此時連接釋放報文的序列號爲 seq=w,ack 也是取第一次揮手的 seq +1,即 ack=u+1,這和第二次揮手時是一樣的。

此時服務端就進入了 LAST_ACK(最後確認) 狀態,等待客戶端的確認,並停止向客戶端發送數據,但服務端仍能夠接收從客戶端傳輸過來的數據。

(4) 第四次揮手

客戶端收到服務器的連接釋放報文後,一樣發送一個 ACK 報文作爲應答 (ack=w+1seq=u+1), 此時客戶端處於 TIME_WAIT(時間等待) 狀態,並在這個狀態等待 2MSL(Two Maximum Segment Lifetime, 最大報文生存時間)。

服務端收到從客戶端發出的 TCP 報文之後結束 LAST-ACK 階段,進入 CLOSED 階段。客戶端等待完 2MSL 之後,結束 TIME-WAIT 階段,進入 CLOSED 階段,由此完成四次揮手。

爲什麼客戶端在 TIME_WAIT 階段要等 2MSL? 主要有以下兩點:

一是爲了保證客戶端發送的最後一個 ACK 報文段能夠到達服務器端,確保服務端能正常進入 CLOSED 狀態。服務端在 1MSL 內沒有收到客戶端發出的 ACK 確認報文,就會再次向客戶端發出 FIN 報文。

二是爲了避免新舊連接混淆。由於網絡滯留,客戶端可能發送了多次請求建立連接的請求,經過時間 2MSL,就可以使本鏈接持續時間內所產生的所有報文段都從網絡中消失,這樣就可以使下一個新的連接中不會出現這種舊的連接請求報文段。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Hl63eQ6Jlhwaa7x0wtgSQg