TCP(Transmission Control Protocol,傳輸控制協議),是一種面向鏈接的可靠傳輸協議,提供可靠(無差錯、不丟失、不重複、按順序)的字節流數據傳輸服務。在傳輸效率和可靠性之間選擇了後者,因此也具備開銷大、傳輸速度慢的缺點。web
TCP 的可靠性傳輸具備很是複雜的實現細節,包括但不限於:緩存
ACK 肯定機制:當接收方接收到數據段後,會返回 ACK 確認。
定時重發:方發送方發送數據段後,會啓動定時器,超時未接收到 ACK 確認,會重發該數據段。
數據校驗:接收方會對數據段進行數據校驗,若是發現數據段有差錯,會將該數據段丟棄,等待超時重傳。
順序傳輸:TCP 字節流會爲每一個字節排序,確保數據傳輸順序的正確性。
滑動窗口:TCP 數據段長度可根據收發雙方的緩存、網絡等狀態而調整。接收方只容許發送方發送接收緩衝區所能接納的數據,防止緩衝區溢出。服務器
TCP 數據段首部:
cookie
其中的 TCP Flags 字段,是很是重要的功能標識,佔 8 位,分別爲:網絡
NOTE:TCP Flags 在整個 TCP 創建/釋放鏈接的過程當中都起到了很是重要的標誌做用。併發
Client IP:172.18.128.204
Server TCP Socket:(10.0.0.128, 80)
curl
172.18.128.204.62534 > 10.0.0.128.80: Flags [S], cksum 0x8523 (correct), seq 3401804541, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 960632545 ecr 0,sackOK,eol], length 0
客戶端執行系統調用 connect()
發出 SYN 請求創建 TCP 鏈接,此時客戶端的 TCP 端口狀態爲 SYN_SENT(表示請求鏈接)。tcp
10.0.0.128.80 > 172.18.128.204.62534: Flags [S.], cksum 0x378d (incorrect -> 0xf136), seq 2666924726, ack 3401804542, win 27960, options [mss 1410,sackOK,TS val 19959035 ecr 960632545,nop,wscale 7], length 0
服務端執行系統調用 listen()
監聽到 SYN 請求,TCP 端口狀態從 LISTEN 轉爲 SYN_RCVD 並第一次響應 ACK。svg
172.18.128.204.62534 > 10.0.0.128.80: Flags [.], cksum 0x7cfb (correct), seq 3401804542, ack 2666924727, win 4106, options [nop,nop,TS val 960632549 ecr 19959035], length 0
客戶端接收到 ACK 響應以後,TCP 端口狀態變成 ESTABLISHED(已創建鏈接,表示通訊雙方正在通訊),再給服務端發送 ACK。服務端接收到 ACK 以後,服務端的 TCP 端口狀態爲 ESTABLISHED。
NOTE:Flags are some combination of S (SYN), F (FIN), P (PUSH), R (RST), W (ECN CWR) or E (ECN-Echo), or a single ‘.’ (no flags)
爲何須要三次握手來保證數據傳輸的可靠性?
「握手」 的行爲實際上爲了告知收發雙方本身的 ISN(Initial Sequence Number,初始化序號),如上文 Client:seq=996980318
或 Server:seq=2180032179
,這個 ISN 會被做爲創建鏈接後進行「順序」數據傳輸的依據。咱們能夠從數據段首部的 Sequence Number 和 Acknowledgment Number 都佔 32 位得知,seq 和 ack 的取值範圍均是 [0, 2^32-1],因此 ISN 實際上會被順序循環使用。並且 seq 並不是每次都是從 0 開始的,TCP 協議會以 4μs 一次的頻率進行 ISN+=1 操做,以此來避免 TCP 重連時出如今同一條鏈接中存在兩個及以上 seq number 相同的數據包,而最終致使順序錯亂。因此,三次握手實際上就是初始化通訊雙方的 seq ISN,保證數據包的有序傳輸。
雙方創建通訊以後,Client 向 Server 正式發出 HTTP 請求:
IP (tos 0x0, ttl 60, id 0, offset 0, flags [DF], proto TCP (6), length 129) 172.18.128.204.62534 > 10.0.0.128.80: Flags [P.], cksum 0xe98f (correct), seq 3401804542:3401804619, ack 2666924727, win 4106, options [nop,nop,TS val 960632549 ecr 19959035], length 77: HTTP, length: 77 GET / HTTP/1.1 Host: 172.18.22.208 User-Agent: curl/7.54.0 Accept: */* IP (tos 0x0, ttl 64, id 34743, offset 0, flags [DF], proto TCP (6), length 52) 10.0.0.128.80 > 172.18.128.204.62534: Flags [.], cksum 0x3785 (incorrect -> 0x8bd8), seq 2666924727, ack 3401804619, win 219, options [nop,nop,TS val 19959040 ecr 960632549], length 0
seq 3401804542:3401804619
== seq 3401804542:[3401804542+length]
Server 處理請求並響應:
IP (tos 0x0, ttl 64, id 34744, offset 0, flags [DF], proto TCP (6), length 295) 10.0.0.128.80 > 172.18.128.204.62534: Flags [P.], cksum 0x3878 (incorrect -> 0x528d), seq 2666924727:2666924970, ack 3401804619, win 219, options [nop,nop,TS val 19959044 ecr 960632549], length 243: HTTP, length: 243 HTTP/1.1 200 OK Date: Thu, 24 Jan 2019 10:08:31 GMT Server: Apache/2.4.6 (CentOS) Last-Modified: Thu, 24 Jan 2019 08:26:02 GMT ETag: "4-5802ff5f8b6b4" Accept-Ranges: bytes Content-Length: 4 Content-Type: text/html; charset=UTF-8 123 IP (tos 0x0, ttl 60, id 0, offset 0, flags [DF], proto TCP (6), length 52) 172.18.128.204.62534 > 10.0.0.128.80: Flags [.], cksum 0x7bae (correct), seq 3401804619, ack 2666924970, win 4099, options [nop,nop,TS val 960632560 ecr 19959044], length 0
NOTE 1:在通過了三次握手以後(Client 和 Server 都肯定了對方的 seq ISN),正式的 HTTP 數據傳輸是在有序進行的。
NOTE 2:能夠看見每個數據包的發出都有相應的 ACK 響應,確保接收方有確切的接收到數據包,不然發送方會啓用超時重發。
TCP 協議規定,對於已經創建的鏈接,收發雙方要進行四次揮手才能成功斷開鏈接,若是缺乏了其中某個步驟,都會使鏈接處於假死狀態,鏈接自己所佔用的資源不會被釋放。
172.18.128.204.62534 > 10.0.0.128.80: Flags [F.], cksum 0x7bad (correct), seq 3401804619, ack 2666924970, win 4099, options [nop,nop,TS val 960632560 ecr 19959044], length 0
由 Client 提出斷開鏈接請求 FIN(Flags [F.],
),Client 的 TCP 端口進入 FIN-WAIT-1 狀態。
10.0.0.128.80 > 172.18.128.204.62534: Flags [F.], cksum 0x3785 (incorrect -> 0x8acb), seq 2666924970, ack 3401804620, win 219, options [nop,nop,TS val 19959053 ecr 960632560], length 0
Server 發送 ACK 應答,Server 的 TCP 端口進入 CLOSE_WAIT 狀態,Client 的 TCP 端口進入 FIN-WAIT-2 狀態。幾乎同時 Server 還發送了一個 FIN 端口鏈接請求,進入 LAST-ACK 狀態。
172.18.128.204.62534 > 10.0.0.128.80: Flags [.], cksum 0x7b9a (correct), seq 3401804620, ack 2666924971, win 4099, options [nop,nop,TS val 960632569 ecr 19959053], length 0
Client 進行 ACK 應答(LAST ACK),進入 TIME-WAIT(兩倍的分段最大生存期),並最終變成 CLOSED 狀態。
TCP 協議規定,對於已經創建的鏈接,收發雙方要進行四次揮手才能成功斷開鏈接,若是缺乏了其中某個步驟,都會使鏈接處於假死狀態,鏈接自己所佔用的資源不會被釋放。實際上,一個網絡服務器常常要同時管理大量的併發鏈接,因此須要保證無用的鏈接被徹底斷開,不然大量假死的鏈接會佔用許多服務器資源。
對於這個問題,咱們要關注 TCP 端口的:CLOSE_WAIT 和 TIME_WAIT 狀態,與 TCP 四次揮手過程密切相關。
能夠經過下面方法來查看 TCP 端口狀態數量:
╭─mickeyfan@localhost ~ ╰─$ netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' 127 ↵ CLOSE_WAIT 1 TIME_WAIT 1 ESTABLISHED 17
LISTENING:網絡服務啓動後首先會處於監聽的狀態
SYN_SENT:表示收發方請求創建鏈接
ESTABLISHED:表示成功創建鏈接,當你要訪問其它的計算機的網絡服務時首先要發個 SYN 信號到特定端口,此時當前服務器的 TCP 端口狀態爲 SYN_SENT,若是鏈接成功了則會變爲 ESTABLISHED。
NOTE:SYN_SENT 狀態通常很是短暫,但若是發現 SYN_SENT 的數量很是多而且在向不一樣的網絡服務器發出,那當前機器可能中了衝擊波或震盪波之類的病毒。這類病毒爲了感染別的計算機,它就要掃描別的計算機,在掃描的過程當中對每一個要掃描的計算機都要發出了 SYN 請求,這也是出現許多 SYN_SENT 的緣由。
close()
斷開鏈接,而且收到對方的 ACK 確認後,我方的 TCP 端口狀態就會變爲 TIME_WAIT。NOTE:TCP 協議規定 TIME_WAIT 狀態會一直持續 2MSL(兩倍的分段最大生存期,240s),以此來保證從新分配的 Socket 不會受到以前殘留的延遲重發報文的影響(保證舊的鏈接狀態不會對新鏈接產生影響)。處於 TIME_WAIT 狀態的鏈接(Socket)佔用的資源不會被內核釋放,因此做爲網絡服務器,在可能的狀況下,儘可能不要主動斷開鏈接。尤爲對於要處理大量短鏈接的服務器,應該由客戶端來主動提出斷開,以減小 TIME_WAIT 狀態形成的資源浪費。若是發現服務器存在大量的 TIME_WAIT,那麼你應該檢查是否有大量的自動斷開鏈接動做存在服務器上。還有這樣的狀況,又我方提出斷開鏈接,但對方一直不給 ACK 應答,我方就會卡在 FIN_WAIT_2 狀態,此時我方默認等到 60 秒(可修改,參考 tcp_max_orphans)。因此這種狀況下我方內存也會被大量無效數據報填滿。
close()
來使得鏈接被正確關閉。NOTE:CLOSE_WAIT 表示我方被動斷開鏈接,若是存在大量的 CLOSE_WAIT,表示我方只在第二次揮手時向對方應答 ACK,並無完成第三次揮手,向對方發送 FIN 請求,這時就多是由於在關閉鏈接以前網絡服務器還有大量的數據要發送或者其餘事要作,致使沒有發送這個 FIN packet。通常是由網絡服務器負載太高,或出現了不可預料的問題致使的。一個 CLOSE_WAIT 會維持至少 2 個小時,若是存在因爲負載一直居高不下,生產了大量的 CLOSE_WAIT,就會形成資源極大的損耗,那麼一般是等不到釋放的那一刻,系統就已經解決崩潰了。
相關的內核參數:
vi /etc/sysctl.conf
net.ipv4.tcp_syncookies = 1
:表示開啓 SYN Cookies。當出現 SYN 等待隊列溢出時,啓用 Cookies 來處理,可防範少許的 SYN 攻擊,默認爲 0,表示關閉.net.ipv4.tcp_tw_reuse = 1
:表示開啓重用。容許將 TIME-WAIT Sockets 從新用於新的 TCP 鏈接,默認爲 0,表示關閉。net.ipv4.tcp_tw_recycle = 1
:表示開啓 TCP 鏈接中 TIME-WAIT Sockets 的快速回收,默認爲 0,表示關閉。net.ipv4.tcp_fin_timeout
:系統等待 FIN_WAIT 超時時間。