TCP 協(xié)議簡述
TCP 提供面向有連接的通信傳輸朴乖,面向有連接是指在傳送數(shù)據(jù)之前必須先建立連接,數(shù)據(jù)傳送完成后要釋放連接短曾。
無論哪一方向另一方發(fā)送數(shù)據(jù)之前,都必須先在雙方之間建立一條連接赐劣。在TCP/IP協(xié)議中嫉拐,TCP協(xié)議提供可靠的連接服務,連接是通過三次握手進行初始化的魁兼。
同時由于TCP協(xié)議是一種面向連接的婉徘、可靠的漠嵌、基于字節(jié)流的運輸層通信協(xié)議,TCP是全雙工模式盖呼,所以需要四次揮手關閉連接儒鹿。
TCP包首部
網(wǎng)絡中傳輸?shù)臄?shù)據(jù)包由兩部分組成:一部分是協(xié)議所要用到的首部,另一部分是上一層傳過來的數(shù)據(jù)几晤。首部的結構由協(xié)議的具體規(guī)范詳細定義约炎。在數(shù)據(jù)包的首部,明確標明了協(xié)議應該如何讀取數(shù)據(jù)蟹瘾。反過來說圾浅,看到首部,也就能夠了解該協(xié)議必要的信息以及所要處理的數(shù)據(jù)憾朴。包首部就像協(xié)議的臉狸捕。
所以我們在學習TCP協(xié)議之前,首先要知道TCP在網(wǎng)絡傳輸中處于哪個位置众雷,以及它的協(xié)議的規(guī)范灸拍,下面我們就看看TCP首部的網(wǎng)絡傳輸起到的作用:
下面的圖是TCP頭部的規(guī)范定義,它定義了TCP協(xié)議如何讀取和解析數(shù)據(jù):
TCP首部承載這TCP協(xié)議需要的各項信息砾省,下面我們來分析一下:
TCP端口號
TCP的連接是需要四個要素確定唯一一個連接:
(源IP鸡岗,源端口號)+ (目地IP,目的端口號)
所以TCP首部預留了兩個16位作為端口號的存儲纯蛾,而IP地址由上一層IP協(xié)議負責傳遞
源端口號和目地端口各占16位兩個字節(jié)纤房,也就是端口的范圍是2^16=65535
另外1024以下是系統(tǒng)保留的,從1024-65535是用戶使用的端口范圍TCP的序號和確認號:
32位序號 seq:Sequence number 縮寫seq 翻诉,TCP通信過程中某一個傳輸方向上的字節(jié)流的每個字節(jié)的序號炮姨,通過這個來確認發(fā)送的數(shù)據(jù)有序,比如現(xiàn)在序列號為1000碰煌,發(fā)送了1000舒岸,下一個序列號就是2000。
32位確認號 ack:Acknowledge number 縮寫ack芦圾,TCP對上一次seq序號做出的確認號蛾派,用來響應TCP報文段,給收到的TCP報文段的序號seq加1个少。-
TCP的標志位
每個TCP段都有一個目的洪乍,這是借助于TCP標志位選項來確定的,允許發(fā)送方或接收方指定哪些標志應該被使用夜焦,以便段被另一端正確處理壳澳。
用的最廣泛的標志是 SYN,ACK 和 FIN茫经,用于建立連接巷波,確認成功的段傳輸萎津,最后終止連接。-
SYN:簡寫為
S
抹镊,同步標志位锉屈,用于建立會話連接,同步序列號垮耳; -
ACK: 簡寫為
.
颈渊,確認標志位,對已接收的數(shù)據(jù)包進行確認氨菇; -
FIN: 簡寫為
F
儡炼,完成標志位,表示我已經(jīng)沒有數(shù)據(jù)要發(fā)送了查蓉,即將關閉連接乌询; - PSH:簡寫為
P
,推送標志位豌研,表示該數(shù)據(jù)包被對方接收后應立即交給上層應用妹田,而不在緩沖區(qū)排隊; - RST:簡寫為
R
鹃共,重置標志位鬼佣,用于連接復位、拒絕錯誤和非法的數(shù)據(jù)包霜浴; - URG:簡寫為
U
晶衷,緊急標志位,表示數(shù)據(jù)包的緊急指針域有效阴孟,用來保證連接不被阻斷晌纫,并督促中間設備盡快處理;
-
SYN:簡寫為
TCP 三次握手建立連接
所謂三次握手(Three-way Handshake)永丝,是指建立一個 TCP 連接時锹漱,需要客戶端和服務器總共發(fā)送3個報文。
三次握手的目的是連接服務器指定端口慕嚷,建立 TCP 連接哥牍,并同步連接雙方的序列號和確認號,交換 TCP 窗口大小信息喝检。在 socket 編程中嗅辣,客戶端執(zhí)行 connect() 時。將觸發(fā)三次握手挠说。
三次握手過程的示意圖如下:
第一次握手:
客戶端將TCP報文標志位SYN置為1澡谭,隨機產生一個序號值seq=J,保存在TCP首部的序列號(Sequence Number)字段里纺涤,指明客戶端打算連接的服務器的端口译暂,并將該數(shù)據(jù)包發(fā)送給服務器端,發(fā)送完畢后撩炊,客戶端進入SYN_SENT
狀態(tài)外永,等待服務器端確認。第二次握手:
服務器端收到數(shù)據(jù)包后由標志位SYN=1知道客戶端請求建立連接拧咳,服務器端將TCP報文標志位SYN和ACK都置為1伯顶,ack=J+1,隨機產生一個序號值seq=K骆膝,并將該數(shù)據(jù)包發(fā)送給客戶端以確認連接請求祭衩,服務器端進入SYN_RCVD
狀態(tài)。第三次握手:
客戶端收到確認后阅签,檢查ack是否為J+1掐暮,ACK是否為1,如果正確則將標志位ACK置為1政钟,ack=K+1路克,并將該數(shù)據(jù)包發(fā)送給服務器端,服務器端檢查ack是否為K+1估脆,ACK是否為1牢硅,如果正確則連接建立成功线欲,客戶端和服務器端進入ESTABLISHED
狀態(tài),完成三次握手灰羽,隨后客戶端與服務器端之間可以開始傳輸數(shù)據(jù)了。
注意:我們上面寫的ack和ACK鱼辙,不是同一個概念:
- 小寫的ack代表的是頭部的確認號Acknowledge number廉嚼, 縮寫ack,是對上一個包的序號進行確認的號座每,ack=seq+1前鹅。
- 大寫的ACK,則是我們上面說的TCP首部的標志位峭梳,用于標志的TCP包是否對上一個包進行了確認操作舰绘,如果確認了,則把ACK標志位設置成1葱椭。
下面我自己做實驗捂寿,開一個HTTP服務,監(jiān)聽80端口孵运,然后使用Tcpdump命令抓包秦陋,看一下TCP三次握手的過程:
sudo tcpdump -n -t -S -i enp0s3 port 80
第一次握手,標志位Flags=S
IP 10.0.2.2.51323 > 10.0.2.15.80: Flags [S], seq 84689409, win 65535, options [mss 1460], length 0
第二次握手治笨,標志位Flags=[S.]
IP 10.0.2.15.80 > 10.0.2.2.51323: Flags [S.], seq 1893430205, ack 84689410, win 64240, options [mss 1460], length 0
第三次握手驳概,標志位Flags=[.]
IP 10.0.2.2.51323 > 10.0.2.15.80: Flags [.], ack 1893430206, win 65535, length 0
建立連接后赤嚼,客戶端發(fā)送http請求
IP 10.0.2.2.51321 > 10.0.2.15.80: Flags [P.], seq 1:753, ack 1, win 65535, length 752: HTTP: GET / HTTP/1.1
tcpdump命令解析一下:
-i : 指定抓包的網(wǎng)卡是enp0s3
-n: 把域名轉成IP顯示
-t: 不顯示時間
-S: 序列號使用絕對數(shù)值,不指定-S的話顺又,序列號會使用相對的數(shù)值
port: 指定監(jiān)聽端口是80
host:指定監(jiān)聽的主機名
我們看下實戰(zhàn)中TCP的三次握手過程:
- 第一次握手更卒,客戶端51323端口號向服務器端80號端口發(fā)起連接,此時標志位flags=S稚照,即SYN=1標志蹂空,表示向服務端發(fā)起連接的請求,同時生成序列號seq=84689409
- 第二次握手果录,服務端標志位flags=[S.]上枕,即SYN+ACK標志位設置為1,表示對上一個請求連接的報文進行確認弱恒,同時設置ack=seq+1=184689410辨萍,生成序列號seq=1893430205
- 第三次握手,客戶端對服務端的響應進行確認返弹,所以此時標志位是[.]即ACK=1分瘦,同時返回對上一個報文的seq的確認號,ack=1893430206
至此琉苇,三次握手完成嘲玫,一個TCP連接建立完成,接下來就是雙端傳輸數(shù)據(jù)了
為什么需要三次握手并扇?
我們假設client發(fā)出的第一個連接請求報文段并沒有丟失去团,而是在某個網(wǎng)絡結點長時間的滯留了,以致延誤到連接釋放以后的某個時間才到達server穷蛹。
本來這是一個早已失效的報文段土陪。但server收到此失效的連接請求報文段后,就誤認為是client再次發(fā)出的一個新的連接請求肴熏。于是就向client發(fā)出確認報文段鬼雀,同意建立連接。
假設不采用“三次握手”蛙吏,那么只要server發(fā)出確認源哩,新的連接就建立了。由于現(xiàn)在client并沒有發(fā)出建立連接的請求鸦做,因此不會理睬server的確認励烦,也不會向server發(fā)送數(shù)據(jù)。但server卻以為新的運輸連接已經(jīng)建立泼诱,并一直等待client發(fā)來數(shù)據(jù)坛掠。這樣,server的很多資源就白白浪費掉了。
所以屉栓,采用“三次握手”的辦法可以防止上述現(xiàn)象發(fā)生舷蒲。例如剛才那種情況,client不會向server的確認發(fā)出確認友多。server由于收不到確認阿纤,就知道client并沒有要求建立連接。
TCP 三次握手跟現(xiàn)實生活中的人與人打電話是很類似的:
三次握手:
“喂夷陋,你聽得到嗎?”
“我聽得到呀胰锌,你聽得到我嗎骗绕?”
“我能聽到你,今天 balabala……”
經(jīng)過三次的互相確認资昧,大家就會認為對方對聽的到自己說話酬土,并且愿意下一步溝通,否則格带,對話就不一定能正常下去了撤缴。
TCP 四次揮手關閉連接
四次揮手即終止TCP連接,就是指斷開一個TCP連接時叽唱,需要客戶端和服務端總共發(fā)送4個包以確認連接的斷開屈呕。在socket編程中,這一過程由客戶端或服務端任一方執(zhí)行close來觸發(fā)棺亭。
由于TCP連接是全雙工的虎眨,因此,每個方向都必須要單獨進行關閉镶摘,這一原則是當一方完成數(shù)據(jù)發(fā)送任務后嗽桩,發(fā)送一個FIN來終止這一方向的連接,收到一個FIN只是意味著這一方向上沒有數(shù)據(jù)流動了凄敢,即不會再收到數(shù)據(jù)了碌冶,但是在這個TCP連接上仍然能夠發(fā)送數(shù)據(jù),直到這一方向也發(fā)送了FIN涝缝。首先進行關閉的一方將執(zhí)行主動關閉扑庞,而另一方則執(zhí)行被動關閉。
四次揮手過程的示意圖如下:
揮手請求可以是Client端拒逮,也可以是Server端發(fā)起的嫩挤,我們假設是Client端發(fā)起:
-
第一次揮手: Client端發(fā)起揮手請求,向Server端發(fā)送標志位是FIN報文段消恍,設置序列號seq岂昭,此時,Client端進入
FIN_WAIT_1
狀態(tài),這表示Client端沒有數(shù)據(jù)要發(fā)送給Server端了约啊。 -
第二次分手:Server端收到了Client端發(fā)送的FIN報文段邑遏,向Client端返回一個標志位是ACK的報文段,ack設為seq加1恰矩,Client端進入
FIN_WAIT_2
狀態(tài)记盒,Server端告訴Client端,我確認并同意你的關閉請求外傅。 -
第三次分手: Server端向Client端發(fā)送標志位是FIN的報文段纪吮,請求關閉連接,同時Client端進入
LAST_ACK
狀態(tài)萎胰。 -
第四次分手 : Client端收到Server端發(fā)送的FIN報文段碾盟,向Server端發(fā)送標志位是ACK的報文段,然后Client端進入
TIME_WAIT
狀態(tài)技竟。Server端收到Client端的ACK報文段以后冰肴,就關閉連接。此時榔组,Client端等待2MSL的時間后依然沒有收到回復熙尉,則證明Server端已正常關閉,那好搓扯,Client端也可以關閉連接了检痰。
為什么連接的時候是三次握手,關閉的時候卻是四次握手锨推?
建立連接時因為當Server端收到Client端的SYN連接請求報文后攀细,可以直接發(fā)送SYN+ACK報文。其中ACK報文是用來應答的爱态,SYN報文是用來同步的谭贪。所以建立連接只需要三次握手。
由于TCP協(xié)議是一種面向連接的锦担、可靠的俭识、基于字節(jié)流的運輸層通信協(xié)議,TCP是全雙工模式洞渔。
這就意味著套媚,關閉連接時,當Client端發(fā)出FIN報文段時磁椒,只是表示Client端告訴Server端數(shù)據(jù)已經(jīng)發(fā)送完畢了堤瘤。當Server端收到FIN報文并返回ACK報文段,表示它已經(jīng)知道Client端沒有數(shù)據(jù)發(fā)送了浆熔,但是Server端還是可以發(fā)送數(shù)據(jù)到Client端的本辐,所以Server很可能并不會立即關閉SOCKET,直到Server端把數(shù)據(jù)也發(fā)送完畢。
當Server端也發(fā)送了FIN報文段時慎皱,這個時候就表示Server端也沒有數(shù)據(jù)要發(fā)送了老虫,就會告訴Client端,我也沒有數(shù)據(jù)要發(fā)送了茫多,之后彼此就會愉快的中斷這次TCP連接祈匙。
為什么要等待2MSL?
MSL:報文段最大生存時間天揖,它是任何報文段被丟棄前在網(wǎng)絡內的最長時間夺欲。
有以下兩個原因:
-
第一點:保證TCP協(xié)議的全雙工連接能夠可靠關閉:
由于IP協(xié)議的不可靠性或者是其它網(wǎng)絡原因,導致了Server端沒有收到Client端的ACK報文今膊,那么Server端就會在超時之后重新發(fā)送FIN些阅,如果此時Client端的連接已經(jīng)關閉處于CLOESD
狀態(tài),那么重發(fā)的FIN就找不到對應的連接了万细,從而導致連接錯亂,所以纸泄,Client端發(fā)送完最后的ACK不能直接進入CLOSED
狀態(tài)赖钞,而要保持TIME_WAIT
,當再次收到FIN的收聘裁,能夠保證對方收到ACK雪营,最后正確關閉連接。 -
第二點:保證這次連接的重復數(shù)據(jù)段從網(wǎng)絡中消失
如果Client端發(fā)送最后的ACK直接進入CLOSED
狀態(tài)衡便,然后又再向Server端發(fā)起一個新連接献起,這時不能保證新連接的與剛關閉的連接的端口號是不同的,也就是新連接和老連接的端口號可能一樣了镣陕,那么就可能出現(xiàn)問題:如果前一次的連接某些數(shù)據(jù)滯留在網(wǎng)絡中谴餐,這些延遲數(shù)據(jù)在建立新連接后到達Client端,由于新老連接的端口號和IP都一樣呆抑,TCP協(xié)議就認為延遲數(shù)據(jù)是屬于新連接的岂嗓,新連接就會接收到臟數(shù)據(jù),這樣就會導致數(shù)據(jù)包混亂鹊碍。所以TCP連接需要在TIME_WAIT狀態(tài)等待2倍MSL厌殉,才能保證本次連接的所有數(shù)據(jù)在網(wǎng)絡中消失。