一.TCP特性概覽
1.面向連接
TCP是基於連接進行數據交互,通信雙方在進行數據交互之前需要建立連接,該連接也只能用在雙方之間進行交互。這點不像UDP中的組播和廣播,可以在同一組中多個主機交互數據。這也是導致TCP協議的復雜性的因素之一,建立連接涉及到三次握手和四次揮手的過程。
2.可靠性保證
- 分段機制。TCP將需要傳輸的數據分成合適的報文段,然后逐個傳輸
- 定時器的超時和重傳機制。TCP對每個發送的報文段都設置一個定時器等待目標端返回確認收到的響應,如果未收到,則超時重傳
- 確認報文機制。接受方收到報文后會返回確認報文告訴發送方收到報文
- 校驗和機制。TCP保持首部和數據的校驗和,接收方將校驗段的校驗和,如果錯誤將丟棄不確認等待發送方超時重傳
- 有序機制。TCP將對報文段進行排序,保證報文段有序性
- 去重機制。TCP將對報文段進行去重
- 流控機制。由於接收方和發送的TCP緩沖區容量不一致和處理速度,需要對發送方的流量進行控制,防止網絡阻塞
3.字節流
TCP雙方進行數據交互時,數據形式為8bit組成的字節流,TCP也不在這些字節流中做任何特殊標識。
這種字節流的特點表現如下:
- TCP不關注數據的具體內容是二進制還是ASCII字符
- 發送方發送的字節流和接收方接受的字節流整體表現一致,但是發送的次數和每次的大小與接受方接收的次數和大小並沒有關系。
二.TCP報文格式

TCP報文段分為兩部分:
- TCP首部。TCP首部是描述TCP報文段的信息,一般為20個字節
- 數據部分。數據部分包含應用層需要傳輸的數據
TCP首部中包含以下信息:
- 16位源端口號和16位目的端口號。網絡中進程通信需要IP + PORT來確定唯一進程。因為IP地址是有IP協議涉及,所以在TCP傳輸中只需要確定接收方和目標方的端口號便可以建立連接,雙方的進程才能正常通信
- 32位序號和32位確認序號。用於標識發送的字節流和期望接收的字節流
- 4位首部長度。表示TCP首部長度,首部中有可選收據長度
- 6位標志位。用於標識不同的連接狀態
- 16位窗口大小。用於流量控制,接收方期望接受的數據大小
- 16位校驗和。TCP首部和TCP數據的校驗碼,由發送方計算生成,接收方用於校驗
- 16位緊急指針。用於發送緊急數據
三.建立和關閉TCP連接
這里使用telent wwww.baidu.com 80和tcpdump -S -t host www.baidu.com演示建立和關閉TCP連接。並通過TCP建立連接和終止連接的時序圖以及報文分析。

其中建立TCP連接需要三次握手,關閉TCP連接需要四次揮手。
1.三次握手
這里將通信雙方分別稱為發送端和接收端。三次握手是應用在發送端和接收端在數據交互前建立TCP連接的過程。這個過程需要三步驟才能完成:
- 發送端為即將建立的連接生成一個SEQ作為TCP報文段中的序號,並將SYN位置1,發送SYN包至接收方,然后進入SYN_SEND狀態
- 接收端在收到SYN包后,進入SYN_RCVD狀態,也生成一個SEQ作為返回給接收端報文的序號,並將接收到的SEQ+1作為返回報文的確認序號,發送SYN包至接收端,且SYN和ACK位置1。該包有兩個作用,作為SYN包進行建立連接,同時作為ACK包回復發送端
- 接收端收到SYN包后,進入ESTABLISHED狀態,表示在發送端連接已經建立。然后將接收到的SYN包的SEQ+1,將結果作為發送給接收端的ACK包的確認序號,且ACK位置1
因為建立連接的過程需要三次交互通信,所以將其稱為三次握手
從tcpdump抓取的報文段可以看出,三次握手的過程:
IP 10.1.133.253.58062 > 183.232.231.174.http: Flags [S], seq 1209507409, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 1469077440 ecr 0,sackOK,eol], length 0
IP 183.232.231.174.http > 10.1.133.253.58062: Flags [S.], seq 2198201899, ack 1209507410, win 8192, options [mss 1452,nop,wscale 5,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,sackOK,eol], length 0
IP 10.1.133.253.58062 > 183.232.231.174.http: Flags [.], ack 2198201900, win 8192, length 0
報文格式為:源IP.端口 > 目標IP.端口 狀態位標志 序號 確認序號 窗口大小 可選項。
Note:為什么需要三次握手?
發送端和接收端各發送一次,需要告訴對方需要建立連接同時包含窗口大小等信息,這是必不可少的二次。由於接收端無法確定發送端是否接受到自己的請求,所以需要發送端發一次回復進行確認。但是接收端為什么不需要回復發送端的ACK,因為這是一個循環依賴的問題,沒有盡頭。所以至少需要三次握手才能基本建立連接。
2.四次揮手
在數據交互完成后,需要關閉連接釋放系統資源,如內存,文件句柄等。因為TCP連接時全雙工模式,需要關閉雙向的數據數據發送。為了保證釋放資源的可靠性,需要四次握手。四次握手的過程中,發送端和接受端都要關閉各自連接釋放資源,從而避免半關閉狀態。四次握手過程:
- 接收端主動發起關閉,向發送端發送FIN包,進入FIN_WAIT狀態,表示接收端方向上將不會再有數據發出,但是同樣還可以收數據
- 為了確保發送端收到了FIN包,發送端需要返回ACK包,確認序號為FIN包的SEQ+1,同時進入CLOSE_WAIT。因為TCP連接時全雙工模式,所以發送端這里不一定需要關閉連接,它仍然可以發送數據
- 待發送端將不再有數據需要發送時,將發送FIN包至接收端,進入LAST_ACK狀態
- 為了確保接收端收到FIN包,需要回復ACK包,同時進入TIME_WAIT狀態。
因為關閉連接的過程有雙方之間的四次交互過程,所以將其稱為四次揮手
IP 183.232.231.174.http > 10.1.133.253.58062: Flags [F.], seq 2198201900, ack 1209507427, win 776, length 0
IP 10.1.133.253.58062 > 183.232.231.174.http: Flags [.], ack 2198201901, win 8192, length 0
IP 10.1.133.253.58062 > 183.232.231.174.http: Flags [F.], seq 1209507427, ack 2198201901, win 8192, length 0
IP 183.232.231.174.http > 10.1.133.253.58062: Flags [.], ack 1209507428, win 776, length 0
3.2MSL的TIME_WAIT
TIME_WAIT是主動發起關閉TCP連接在四次揮手過程中出現的連接狀態。當主動發起關閉連接的一方在接收到被動關閉方請求的FIN包后,將響應ACK包后,將進入該狀態。該狀態的持續時間為2MSL時間,處於該狀態的TCP連接將等待關閉。
MSL(Maximum Segment Lifetime)是報文段在網絡中存活的最長時間,超過這個時間的報文將會被丟棄。
即主動關閉TCP連接一方在發送ACK包時,必須等待TIME_WAIT狀態2倍MSL時間。TCP協議這樣處理主要有以下兩方面原因:
-
確保被動關閉一方未能接收到主動關閉連接一方發的ACK包時能夠重發FIN包。因為因為網絡問題,被動關閉一方可能無法接收到最后的ACK包,而不能正常關閉連接。這時被動關閉方可以重新發一個FIN包給主動關閉方,由於主動關閉方處於TIME_WAIT狀態,仍然可以恢復ACK包。由於ACK包和FIN包都在網絡中停留最長時間為MSL,所以TIME_WAIT為2MSL,保證盡可能最長的時間接受被動關閉方的FIN包。如果主動關閉方在恢復最后的ACK包后,不經過TIME_WAIT狀態而直接CLOSED,那么可能存在被動關閉方未能接收到ACK包,從而超時重傳FIN包,這時主動關閉方的TCP連接已經關閉,在接收到這個FIN包后尋找不到對應的連接,從而回復RST復位包,被動關閉方接收到RST包后會處理錯誤從而導致無法正常關閉連接釋放資源。
-
避免前一個連接產生的報文由於在網絡中停留從而與新連接的報文耦合在一起產生數據錯誤。都知道代表TCP連接的標識是(源IP,源端口號,目的IP,目的端口號)。因為TCP連接時全雙工的雙向通信,關閉時需要雙端都關閉保證雙向都沒有數據流通。當主動方發起關閉,只能保證主動方將沒有任何數據向外發送。此時被動仍然可能會發送報文,加入這些報文在網絡節點中停留,然后主動方又立即與被動方建立新的TCP連接且新的連接和上次TCP連接的標識是一樣的,此時在網絡中停留的報文也剛好抵達主動方,主動方處理該報文必然會發生錯誤。主動關閉方在停留2MSL的TIME_WAIT狀態,保證TCP連接中網絡報文盡可能的過期。
總結來說,主動發起關閉TCP連接的一方停留在TIME_WAIT狀態2倍的MSL時間主要為了:
- 為了被動關閉一方能夠重傳FIN包,保證正常的關閉TCP連接釋放資源
- 避免上一次TCP連接的網絡中殘留報文耦合干擾新的TCP連接
當2MSL過后,TCP連接將正式被關閉CLOSED釋放資源。但是停留在TIME_WAIT狀態的TCP連接有以下的特征:
-
處於這種狀態的TCP連接將會仍然占用該端口號。比如建立TCP連接的源端口好為52222,當發起方主動關閉TCP連接,當處於TIME_WAIT狀態式,該端口將被占用
-
TCP連接沒有被關閉,所以資源仍然沒有被釋放,比如:內存、CPU、IO等
以下是筆者獲取本機的網絡連接,通過netstat -an發現:

其中端口為61321的TCP連接處於TIME_WAIT狀態,然后通過Java的ServerSocket監聽61321端口:
ServerSocket serverSocket = new ServerSocket();
SocketAddress address = new InetSocketAddress("10.1.133.253", 61321);
serverSocket.setReuseAddress(false);
serverSocket.bind(address);
在運行以上代碼時拋出以下異常:

對於以上問題,可以通過設置Socket參數SO_REUSEADDR來改變以上的行為,如果該參數為TRUE,則可以重復使用綁定端口,從而避免TIME_WAIT導致端口被占用的問題。
對於客戶端這兩點一般影響不是很大,但是對於服務端而言,如果出現大量的TIME_WAIT狀態的連接,可能會造成服務端的性能大幅度下降,處理能力減弱,甚至嚴重服務器崩潰癱瘓。因為大量的網絡連接沒有及時釋放,占用大量的系統資源,導致無法處理新的連接和請求。
對於這種問題一般的解決方式為兩種方式:
-
從操作系統層進行優化。通過配置系統參數,從而解決該問題
-
客戶端發起連接關閉,避免服務端主動關閉連接。比如:客戶端使用完Socket連接后,主動進行關閉釋放資源,這樣服務端將不會存在TIME_WAIT
修改/etc/sysctl.conf配置,加入:
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_tw_reuse = 1表示開啟重用,可以將TIME_WAIT狀態的Socket重新用於新的TCP連接,默認為0表示關閉;
net.ipv4.tcp_tw_recycle = 1表示開啟接快速回收TIME_WAIT狀態的TCP連,默認為0表示關閉;
net.ipv4.tcp_fin_timeout = 30修改默認的TIME_WAIT的時間,改為30s;
4.RST報文的作用
在前文中介紹了TCP報文格式時,提到幾種狀態標志位,其中有RST標志位。當該標志位為1時表示報文段是RST報文。在TCP的設計中RST報文有以下幾種作用:
- 不存在的端口的連接請求
當想沒有使用的端口上發送報文時,目標端將會恢復RST報文。這個可以用檢測目標端口是否被監聽。
- 異常終止一個連接
當應用發生異常時,終止連接釋放資源,此時將發生RST報文到對端。而非通過FIN這種正常關閉連接,可以達到資源快速釋放。比如對端可能會出現:
read error: Connection reset by peer
這種情況表明,對端發生異常,及時關閉TCP連接回復了RST報文。
- 檢測半打開的連接
TCP是全雙工的雙向通信模式,當關閉釋放資源時,需要雙端都關閉連接進行資源釋放。這樣的情況就存在一端關閉了TCP連接,而另一端沒有關閉連接。這種狀態被稱為半關閉狀態。
對於高訪問量的服務型應用而言,經常都會使用連接池這種長連接方式,這種情況經常會出現半關閉狀態。應為通信雙端無法感知彼此連接狀態的變化,大多數應用都通過心跳檢測,斷連重連解決。
心跳檢測的原理即通過持續隔斷的發送心跳數據至對端,對端回復相應的數據來檢測連接的正常有效性。當出現回復超時,或者連接被對端斷開的情形式可以認為連接失效,這時可以關閉該端連接,並進行重連。
其中連接被對端斷開的情形,就和第一種情況類似,對端已經不存在該連接的信息,從而回復RST報文,表示該連接已經被對端關閉。
該種情況與第一種的區別在於,第一種情況是嘗試與未監聽的端口上建立連接過程被回復RST報文。而該中情況是在已建立的連接上發送報文,由於對端關閉,從而回復RST報文。
總結
本篇文章介紹了TCP的基礎特性,連接建立和終止的過程以及相關狀態的含義,其中針對2MSL狀態和RST報文段的作用做了說明。這些內容主要都介紹TCP的面向連接的特性,對於TCP另一個非常重要的特征-可靠性后續文章介紹。