TCP是一種面向鏈接的、可靠的、基於字節流的傳輸層通訊協議,在發送數據前,通訊雙方必須在彼此間創建一條鏈接。所謂的「鏈接」,實際上是客戶端和服務端保存的一份關於對方的信息,如ip地址、端口號等。面試
一個TCP鏈接一般分爲三個階段:鏈接、數據傳輸、退出(關閉)。經過三次握手創建一個連接,經過四次揮手來關閉一個鏈接。算法
在瞭解TCP鏈接以前先來了解一下TCP報文的頭部結構。 緩存
源端口號
標示這段報文來自哪裏;目的端口號
標示這段報文要發往哪裏。進行tcp通訊時,通常客戶端是經過系統自動選擇的臨時端口號,而服務器通常是使用指定的端口號。服務器
由於TCP使用IP來傳輸報文段,而IP不能過濾掉重複的報文,也不能保證報文的順序。好比客戶端給服務端發送一條5kb的數據,若是tcp一次只能發1kb,那就要將數據分紅5段分5次來發,將這5段數據分別編號爲一、二、三、四、5,發送的時候按照這個順序進行發送,服務端收到的順序可能就是13254,也就是說服務端收到的順序和發送的順序多是不一致的,甚至由於某些因素(好比客戶端沒收到服務端回覆的ACK數據包就會重發數據)致使服務端收到多個編號相同的數據。網絡
那這樣的話服務端收到所有數據後要如何將它們拼接成正確的數據呢?這裏就用到了序列號。首先序列號被系統初始化爲一個隨機值ISN
,一個報文的序列號就是ISN
+這個報文攜帶的數據的第一個字節的偏移量。好比上面所說的例子要發5個報文,第一個報文的第一個字節的偏移量爲0,序列號就是ISA+0;因爲第一個報文攜帶的數據大小是1kb(1024),因此第二個報文的第一個字節的偏移量就是1024,序列號就是ISA+1024,以此類推。(注意實際上三次握手時是會佔據一個序列號的,因此實際上正式發送數據時第一個報文的序列號是ISA+1+0,這裏爲了方便理解就不考慮三次握手時佔據的那個序列號)。tcp
仍是用前面的例子,服務端收到客戶端發過來的報文後須要給客戶端回覆一個ack數據包,回覆報文的確認號的值等於服務端收到的報文的序列號的值+1,好比服務端當前收到的報文的序列號是ISN+2048
(也就是第3段數據),那麼它回覆客戶端的報文的確認號的值就是ISN+2048+1
,其做用就是告訴客戶端ISN+2048+1
以前的全部數據都已經收到了。指針
關於ack回覆有幾點須要說明(下面客戶端爲發送端,服務端爲接收端):code
a.客戶端發送一個報文後並不須要等服務端的ack回覆就能夠接着發下一條報文。cdn
b.服務端回覆ack時,必需要確保確認號以前的數據所有已經收到了,好比上面的例子,若是序列號是ISN+2048
的報文收到了,可是ISN+1024
的報文還沒收到,那就不能回覆ISN+2048+1
的ack。blog
c.服務端在收到數據後不是當即給客戶端發送ack的,通常會有200ms的延遲(系統有個定時器每隔200ms來檢查是否須要發送ack包)。這麼作是由於TCP數據包到達的順序是不保證的,就好比上面必需要等ISN+1024
的數據收到了才能回覆ISN+2048+1
的ack,這個時候就只用回覆ISN+2048+1
這一個ack就能夠了,不須要回復ISN+1024+1
的ack了,由於回覆ISN+2048+1
的ack就已經告訴客戶端ISN+2048+1
以前的數據已經所有收到了,這樣作還能夠減小網絡流量。固然若是ISN+1024
的數據丟包致使服務端一直沒收到,那客戶端也就一直收不到ack回覆,客戶端就會從上次收到的ack回覆開始重發數據,包括服務端已經收到的ISN+2048
數據也會重發。另外若是服務端恰好也有數據要發給客戶端,那麼就會在發送數據的TCP數據包裏帶上ack信息。
頭部長度是以32bit(4字節)爲一個單位,頭部長度的值表示TCP頭部總共多少個32bit,4位的最大是二進制1111(十進制15),TCP頭部最大爲15*32/8
個字節,也就是60字節。TCP頭部前面20個字節是固定的,TCP頭部最小也就是20個字節。
URG
:爲1時表示緊急指針有效。
ACK
:爲1時表示確認號是有效的,攜帶ACK標誌的報文段也稱確認報文段。
PSH
:爲1時是提示接收端應用程序應該當即從TCP接受緩衝區中讀走數據,爲後續接收的數據讓出空間。
RST
:爲1時表示通知對方關閉鏈接或從新創建鏈接。帶RST標誌的TCP報文段也叫復位報文段。不少異常狀況都會致使沒法創建TCP鏈接或者TCP鏈接異常終止,好比客戶端請求中使用了一個不存在的端口,那服務端就能夠發送RST報文段拒絕這個請求;再好比TCP鏈接好久沒有傳輸數據了,能夠發送RST報文段來終止這個鏈接;還好比TCP鏈接出現了一次,而後服務端但願終止這條異常的連接,能夠發送RST報文段來終止這個鏈接。注意一旦發送了復位報文段,發送端全部排隊等待發送的數據都將被丟棄,並且發送完RST報文後TCP鏈接就關閉了,因此接收端收到RST報文後也就沒有必要發送ACK包來確認了。
SYN
:爲1表示創建一個鏈接,攜帶SYN標誌的報文段爲同步報文段。SYN標誌位只有在TCP創建鏈接時(也就是三次握手的時候)纔會被置爲1,客戶端請求創建鏈接時(第一次握手)的報文就攜帶SYN標誌和初始化的序列號(也就是起始序列號),SYN標誌是提醒服務端記住客戶端的起始序列號。而後服務端也會初始化本身的起始序列號並回復客戶端一條報文(第二次握手),這條報文包含服務端的起始序列號、SYN標誌位和ACK標誌位,其中SYN標誌位用來提醒客戶端記住服務端的起始序列號。
FIN
:爲1時用來告知對方本端要關閉鏈接了。
窗口大小能夠理解爲本身接收數據的緩存區大小,用來告訴對方本身最大還能接收多少數據,若是對方發送的數據超過了窗口大小,那這個數據會被丟棄。當窗口大小爲0時對方會暫停發送數據,直到窗口大小大於0時纔會恢復發送數據。
發送端對TCP報文(包括TCP頭部和數據部分)執行CRC算法獲得校驗和,接收端收到報文後對報文執行一樣的算法,將結果與校驗和進行比對,二者一致說明TCP報文段在傳輸過程當中沒有損壞,若是不一致那麼這段TCP報文段會被直接丟棄。
只有當URG標誌位爲1時緊急指針纔是有意義的,緊急指針指向緊急數據最後一個字節的下一位。準確來講緊急指針的值實際上是一個相對於序列號的偏移量,好比說緊急指針的值是5,若是這個報文段的序列號seq=10,那就表示緊急指針指向的是15這個位置的字節,這就意味着10到14這段數據是緊急數據。緊急數據是不進入緩存區的。(注:關於緊急指針的指向和緊急數據的長度網上有不一樣的說法,我這裏也不能肯定哪一種是正確的)。
TCP頭部選項時長度可變的可選信息,最多包含40個字節。典型的TCP頭部選項包含kind
、length
和info
三個部分,kind表示選項的類型,length表示選項的長度,info表示選項的具體內容。
第一次握手
:客戶端要向服務端發起鏈接請求,首先客戶端隨機生成一個起始序列號ISN(好比是100),那客戶端向服務端發送的報文段包含SYN標誌位(也就是SYN=1),序列號seq=100。
第二次握手
:服務端收到客戶端發過來的報文後,發現SYN=1,知道這是一個鏈接請求,因而將客戶端的起始序列號100存起來,而且隨機生成一個服務端的起始序列號(好比是300)。而後給客戶端回覆一段報文,回覆報文包含SYN和ACK標誌(也就是SYN=1,ACK=1)、序列號seq=300、確認號ack=101(客戶端發過來的序列號+1)。
第三次握手
:客戶端收到服務端的回覆後發現ACK=1而且ack=101,因而知道服務端已經收到了序列號爲100的那段報文;同時發現SYN=1,知道了服務端贊成了此次鏈接,因而就將服務端的序列號300給存下來。而後客戶端再回復一段報文給服務端,報文包含ACK標誌位(ACK=1)、ack=301(服務端序列號+1)、seq=101(第一次握手時發送報文是佔據一個序列號的,因此此次seq就從101開始,須要注意的是不攜帶數據的ACK報文是不佔據序列號的,因此後面第一次正式發送數據時seq仍是101)。當服務端收到報文後發現ACK=1而且ack=301,就知道客戶端收到序列號爲300的報文了,就這樣客戶端和服務端經過TCP創建了鏈接。
第一次揮手
:當客戶端的數據都傳輸完成後,客戶端向服務端發出鏈接釋放報文(固然數據沒發完時也能夠發送鏈接釋放報文並中止發送數據),釋放鏈接報文包含FIN標誌位(FIN=1)、序列號seq=1101(100+1+1000,其中的1是創建鏈接時佔的一個序列號)。須要注意的是客戶端發出FIN報文段後只是不能發數據了,可是還能夠正常收數據;另外FIN報文段即便不攜帶數據也要佔據一個序列號。第二次揮手
:服務端收到客戶端發的FIN報文後給客戶端回覆確認報文,確認報文包含ACK標誌位(ACK=1)、確認號ack=1102(客戶端FIN報文序列號1101+1)、序列號seq=2300(300+2000)。此時服務端處於關閉等待狀態,而不是立馬給客戶端發FIN報文,這個狀態還要持續一段時間,由於服務端可能還有數據沒發完。第三次揮手
:服務端將最後數據(好比50個字節)發送完畢後就向客戶端發出鏈接釋放報文,報文包含FIN和ACK標誌位(FIN=1,ACK=1)、確認號和第二次揮手同樣ack=110二、序列號seq=2350(2300+50)。第四次揮手
:客戶端收到服務端發的FIN報文後,向服務端發出確認報文,確認報文包含ACK標誌位(ACK=1)、確認號ack=235一、序列號seq=1102。注意客戶端發出確認報文後不是立馬釋放TCP鏈接,而是要通過2MSL(最長報文段壽命的2倍時長)後才釋放TCP鏈接。而服務端一旦收到客戶端發出的確認報文就會立馬釋放TCP鏈接,因此服務端結束TCP鏈接的時間要比客戶端早一些。由於須要考慮鏈接時丟包的問題,若是隻握手2次,第二次握手時若是服務端發給客戶端的確認報文段丟失,此時服務端已經準備好了收發數(能夠理解服務端已經鏈接成功)據,而客戶端一直沒收到服務端的確認報文,因此客戶端就不知道服務端是否已經準備好了(能夠理解爲客戶端未鏈接成功),這種狀況下客戶端不會給服務端發數據,也會忽略服務端發過來的數據。
若是是三次握手,即使發生丟包也不會有問題,好比若是第三次握手客戶端發的確認ack報文丟失,服務端在一段時間內沒有收到確認ack報文的話就會從新進行第二次握手,也就是服務端會重發SYN報文段,客戶端收到重發的報文段後會再次給服務端發送確認ack報文。
由於只有在客戶端和服務端都沒有數據要發送的時候才能斷開TCP。而客戶端發出FIN報文時只能保證客戶端沒有數據發了,服務端還有沒有數據發客戶端是不知道的。而服務端收到客戶端的FIN報文後只能先回復客戶端一個確認報文來告訴客戶端我服務端已經收到你的FIN報文了,但我服務端還有一些數據沒發完,等這些數據發完了服務端才能給客戶端發FIN報文(因此不能一次性將確認報文和FIN報文發給客戶端,就是這裏多出來了一次)。
這裏一樣是要考慮丟包的問題,若是第四次揮手的報文丟失,服務端沒收到確認ack報文就會重發第三次揮手的報文,這樣報文一去一回最長時間就是2MSL,因此須要等這麼長時間來確認服務端確實已經收到了。
TCP設有一個保活計時器,客戶端若是出現故障,服務器不能一直等下去,白白浪費資源。服務器每收到一次客戶端的請求後都會從新復位這個計時器,時間一般是設置爲2小時,若兩小時尚未收到客戶端的任何數據,服務器就會發送一個探測報文段,之後每隔75秒鐘發送一次。若一連發送10個探測報文仍然沒反應,服務器就認爲客戶端出了故障,接着就關閉鏈接。