TCP是基於鏈接進行數據交互,通訊雙方在進行數據交互以前須要創建鏈接,該鏈接也只能用在雙方之間進行交互。這點不像UDP中的組播和廣播,能夠在同一組中多個主機交互數據。這也是致使TCP協議的複雜性的因素之一,創建鏈接涉及到三次握手和四次揮手的過程。服務器
TCP雙方進行數據交互時,數據形式爲8bit組成的字節流,TCP也不在這些字節流中作任何特殊標識。網絡
這種字節流的特色表現以下:tcp
TCP報文段分爲兩部分:性能
TCP首部中包含如下信息:優化
這裏使用telent wwww.baidu.com 80和tcpdump -S -t host www.baidu.com演示創建和關閉TCP鏈接。並經過TCP創建鏈接和終止鏈接的時序圖以及報文分析。操作系統
其中創建TCP鏈接須要三次握手,關閉TCP鏈接須要四次揮手。設計
這裏將通訊雙方分別稱爲發送端和接收端。三次握手是應用在發送端和接收端在數據交互前創建TCP鏈接的過程。這個過程須要三步驟才能完成:3d
由於創建鏈接的過程須要三次交互通訊,因此將其稱爲三次握手指針
從tcpdump抓取的報文段能夠看出,三次握手的過程:code
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,由於這是一個循環依賴的問題,沒有盡頭。因此至少須要三次握手才能基本創建鏈接。
在數據交互完成後,須要關閉鏈接釋放系統資源,如內存,文件句柄等。由於TCP鏈接時全雙工模式,須要關閉雙向的數據數據發送。爲了保證釋放資源的可靠性,須要四次握手。四次握手的過程當中,發送端和接受端都要關閉各自鏈接釋放資源,從而避免半關閉狀態。四次握手過程:
由於關閉鏈接的過程有雙方之間的四次交互過程,因此將其稱爲四次揮手
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
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時間主要爲了:
當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;
在前文中介紹了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另外一個很是重要的特徵-可靠性後續文章介紹。