TCP 協(xié)議是網(wǎng)絡(luò)通信中最核心的協(xié)議之一,位于七層網(wǎng)絡(luò)協(xié)議的第四層:傳輸層虹蓄。
TCP 數(shù)據(jù)包結(jié)構(gòu)
整個 TCP 數(shù)據(jù)包的結(jié)構(gòu)如上圖所示池凄,是一個嵌套結(jié)構(gòu)枣察,從外到內(nèi)依次是以太網(wǎng)數(shù)據(jù),IP數(shù)據(jù)砰盐,TCP數(shù)據(jù)闷袒。
以太網(wǎng)的數(shù)據(jù)包也叫幀是網(wǎng)絡(luò)傳輸?shù)幕締卧笮∈枪潭ǖ睦憧ǎ畛跏亲畲?1518 字節(jié)(18 字節(jié)標(biāo)頭 + 1500 字節(jié)數(shù)據(jù))霜运,后來在以太網(wǎng)標(biāo)頭增加 4 字節(jié)可選標(biāo)識變?yōu)樽畲?1522 字節(jié)脾歇。
IP 數(shù)據(jù)數(shù)據(jù)包則在以太網(wǎng)數(shù)據(jù)包里面蒋腮,有自己的頭信息至少 20 字節(jié),所以IP數(shù)據(jù)包的數(shù)據(jù)大小為 1480 字節(jié)藕各。
TCP 數(shù)據(jù)在IP數(shù)據(jù)包里面池摧,一般有 20~60 字節(jié)的頭信息,所以 TCP 數(shù)據(jù)包的大小一般為 1460~1420 字節(jié)激况。
TCP 頭部信息
- SourcePort 和 DestinationPort:16 位源端口號和 16 位目的端口號作彤。
- Sequence Number:32 位序號,一次 TCP 通信過程中某一個傳輸方向上的字節(jié)流的每個字節(jié)的編號乌逐,通過這個來確認(rèn)發(fā)送的數(shù)據(jù)有序竭讳,比如現(xiàn)在序列號為 1000,發(fā)送了 1000浙踢,下一個序列號就是 2000绢慢。
- Acknowledgement Number:32 位確認(rèn)號,用來響應(yīng) TCP 報文段洛波,給收到的 TCP 報文段的序號加 1胰舆,三握時還要攜帶自己的序號。
- Data Offset:標(biāo)識該 TCP 頭部有多少個 4 字節(jié)蹬挤,因為 4 位最大能表示 15缚窿,所以 TCP 頭部最長是 60字節(jié)。
- Reserved:保留位—須置 0焰扳。
- Flags:
- NS—ECN-nonce倦零。ECN 顯式擁塞通知(Explicit Congestion Notification)是對 TCP 的擴展,定義于 RFC 3540(2003)吨悍。ECN 允許擁塞控制的端對端通知而避免丟包光绕。ECN 為一項可選功能,如果底層網(wǎng)絡(luò)設(shè)施支持畜份,則可能被啟用 ECN 的兩個端點使用诞帐。在 ECN 成功協(xié)商的情況下,ECN 感知路由器可以在 IP 頭中設(shè)置一個標(biāo)記來代替丟棄數(shù)據(jù)包爆雹,以標(biāo)明阻塞即將發(fā)生停蕉。數(shù)據(jù)包的接收端回應(yīng)發(fā)送端的表示愕鼓,降低其傳輸速率,就如同在往常中檢測到包丟失那樣慧起。
- CWR—Congestion Window Reduced菇晃,定義于 RFC 3168(2001)。
- ECE—ECN-Echo 有兩種意思蚓挤,取決于 SYN 標(biāo)志的值磺送,定義于 RFC 3168(2001)。
- URG—為 1 表示高優(yōu)先級數(shù)據(jù)包灿意,緊急指針字段有效估灿。
- ACK—為 1 表示確認(rèn)號字段有效。
- PSH—為 1 表示是帶有 PUSH 標(biāo)志的數(shù)據(jù)缤剧,指示接收方應(yīng)該盡快將這個報文段交給應(yīng)用層而不用等待緩沖區(qū)裝滿馅袁。
- RST—為 1 表示出現(xiàn)嚴(yán)重差錯』脑可能需要重新創(chuàng)建 TCP 連接汗销。還可以用于拒絕非法的報文段和拒絕連接請求。
- SYN—為 1 表示這是連接請求或是連接接受請求抵窒,用于創(chuàng)建連接和使順序號同步
- FIN—為 1 表示發(fā)送方?jīng)]有數(shù)據(jù)要傳輸了弛针,要求釋放連接。
- Window:16 位窗口大小李皇,TCP 流量控制的一個手段削茁,用來告訴對端 TCP 緩沖區(qū)還能容納多少字節(jié)。
- Checksum:16 位校驗和疙赠,由發(fā)送端填充付材,接收端對報文段執(zhí)行 CRC 算法以檢驗 TCP 報文段在傳輸中是否損壞。
- Urgent Pointer:16 位緊急指針圃阳,一個正的偏移量厌衔,它和序號段的值相加表示最后一個緊急數(shù)據(jù)的下一字節(jié)的序號。
- Option:TCP 選項捍岳,可變長的可選信息富寿,這部分最多包含 40 字節(jié)。選項的第一個字段為選項的類型 kind锣夹,有的 TCP 選項沒有后面兩個字段页徐,僅包含 1 字節(jié)的 kind 字段。第二個字段 length(如果有的話)指定該選項的總長度银萍,該長度包括 kind 字段和 length 字段占據(jù)的 2 字節(jié)变勇,第三個字段 info(如果有的話)是選項的具體信息。
TCP鏈接建立和斷開
TCP 使用三次握手過程建立連接,使用四次揮手過程斷開連接搀绣。
三次握手
TCP 用三次握手(或稱三路握手飞袋,three-way handshake)過程創(chuàng)建一個連接。在連接創(chuàng)建過程中链患,很多參數(shù)要被初始化巧鸭,例如序號被初始化以保證按序傳輸和連接的強壯性。
TCP 連接的正常建立
通常是由一端(服務(wù)器端)打開一個套接字(socket)然后監(jiān)聽來自另一方(客戶端)的連接麻捻,這就是通常所指的被動打開(passive open)纲仍。服務(wù)器端被被動打開以后,客戶端就能開始創(chuàng)建主動打開(active open)贸毕。
三次握手協(xié)議的過程
- 客戶端向服務(wù)器端發(fā)送一個 SYN 包郑叠,請求一個主動打開。該包攜帶客戶端為這個連接請求而設(shè)定的隨機數(shù) A 作為消息序號(Sequence Number)崖咨《途校客戶端的狀態(tài)變成: SYN_SEND油吭。
- 服務(wù)器端收到一個合法的 SYN 包后击蹲,把該包放入 SYN 隊列中;回送一個 SYN/ACK婉宰。ACK 的確認(rèn)碼應(yīng)為 A+1歌豺,SYN/ACK 包本身攜帶一個隨機產(chǎn)生的序號 B。服務(wù)端狀態(tài)變成 SYN_RECEVIED心包。
- 客戶端收到 SYN/ACK 包后类咧,發(fā)送一個 ACK 包,該包的序號被設(shè)定為 A+1蟹腾,而 ACK 的確認(rèn)碼則為B+1痕惋。客戶端狀態(tài)變成 ESTABLISHED娃殖,服務(wù)端收到確認(rèn)后也變成 ESTABLISHED值戳。
看到有位博主的圖片非常有意思,用來比喻三次握手非常形象炉爆,哈哈堕虹。
四次揮手
TCP 連接終止使用四次揮手過程(或稱四次握手,four-way handshake)芬首,在這個過程中連接的每一側(cè)都獨立地被終止赴捞。當(dāng)一個端點要停止它這一側(cè)的連接,就向?qū)?cè)發(fā)送 FIN郁稍,對側(cè)回復(fù) ACK 表示確認(rèn)赦政。拆掉一側(cè)的連接過程需要一對 FIN 和 ACK,分別由兩側(cè)端點發(fā)出耀怜。終止鏈接可以由雙方任意方發(fā)起恢着,下面只是按照客戶端方發(fā)起描述掸屡。
四次揮手協(xié)議過程
- 客戶端向服務(wù)器端發(fā)送一個 FIN 包,請求關(guān)閉連接然评,SEQ=A仅财。客戶端的狀態(tài)變成 FIN1_WAIT碗淌。
- 服務(wù)器端收到后返回 ACK=A+1盏求,服務(wù)端狀態(tài)變成 CLOSE_WAIT,客戶端狀態(tài)變?yōu)?FIN_WAIT2亿眠。
- 服務(wù)端向客戶端器端發(fā)送一個 FIN 包碎罚,請求關(guān)閉連接,SEQ=B纳像【A遥客戶端的狀態(tài)變成 LAST_ACK。
- 客戶器端收到后返回 ACK=B+1竟趾。服務(wù)端狀態(tài)變成 CLOSE憔购,客戶端狀態(tài)變?yōu)?TIME_WAIT。
發(fā)出 FIN 的一側(cè)岔帽,如果給對側(cè)的 FIN 響應(yīng)了 ACK(TIME_WAIT)玫鸟,那么就會等待 2*MSL 時間,然后關(guān)閉連接犀勒。在這段超時等待時間內(nèi)屎飘,本地的端口不能被新連接使用(避免延時到達(dá)的包與隨后的新連接混淆)。參數(shù) tcp_max_tw_buckets 控制并發(fā)的 TIME_WAIT 的數(shù)量贾费,默認(rèn)值是 180000钦购,如果超限,系統(tǒng)會把多的 TIME_WAIT 狀態(tài)的連接清理掉褂萧。
PS:用 wireshark 之類分析 TCP 數(shù)據(jù)包的時候會發(fā)現(xiàn)四次揮手過程往往只有 3 個數(shù)據(jù)包押桃,這是因為 TCP 規(guī)定 ACK 可以捎帶在其他數(shù)據(jù)包當(dāng)中,所以你看到的主動斷開連接一方本應(yīng)收到的 ACK箱玷,是被對方的 FIN 包捎帶過來的怨规,就變成了三個包了。但是如果當(dāng)被動方還有數(shù)據(jù)未發(fā)送完的時候就會有如圖所示的四次交互了锡足,也是因為這樣才不能稱為三次揮手波丰。
ACK 和滑動窗口
可靠傳輸 ACK
通常在每個 TCP 報文段中都有一對序號和確認(rèn)號。TCP 報文發(fā)送者稱自己的字節(jié)流的編號為序號(Sequence Number)舶得,稱接收到對方的字節(jié)流編號為確認(rèn)號(Acknowledgement Number)掰烟。
發(fā)送的時候,TCP 協(xié)議為每個包編號(Sequence Number),以便接收的一方按照順序還原纫骑。萬一發(fā)生丟包蝎亚,也可以知道丟失的是哪一個包。
第一個包的編號是一個隨機數(shù)先馆。為了便于理解发框,這里就把它稱為 1 號包。假定這個包的負(fù)載長度是 100 字節(jié)煤墙,那么可以推算出下一個包的編號應(yīng)該是 101(握手梅惯、揮手階段是+1)。這就是說仿野,每個數(shù)據(jù)包都可以得到兩個編號:自身的編號铣减,以及下一個包的編號。接收方由此知道脚作,應(yīng)該按照什么順序?qū)⑺鼈冞€原成原始文件葫哗。
TCP 報文的接收者為了確保可靠性球涛,在接收到一定數(shù)量的連續(xù)字節(jié)流后發(fā)送確認(rèn)劣针。這是對 TCP 的一種擴展,稱為選擇確認(rèn)(Selective Acknowledgement)宾符,ACK 的值=收到包序號+數(shù)據(jù)長度酿秸。選擇確認(rèn)使得 TCP 接收者可以對亂序到達(dá)的數(shù)據(jù)塊進(jìn)行確認(rèn)灭翔。
發(fā)送窗口
接收方在“窗口大小”域指出還可接收的字節(jié)數(shù)量魏烫。發(fā)送方在沒有新的確認(rèn)包的情況下至多發(fā)送“窗口大小”允許的字節(jié)數(shù)量。接收方可修改“窗口大小”的值肝箱。
通過 ACK 以及發(fā)送窗口進(jìn)行傳輸數(shù)據(jù)過程如下圖所示哄褒。
滑動窗口
TCP 以 1 個段為單位呐赡,每發(fā)送一個段進(jìn)行一次確認(rèn)應(yīng)答的處理。這樣的傳輸方式有一個缺點骏融,就是包的往返時間越長通信性能就越低链嘀。
為解決這個問題,TCP 引入了窗口這個概念档玻。確認(rèn)應(yīng)答不再是以每個分段怀泊,而是以更大的單位進(jìn)行確認(rèn),轉(zhuǎn)發(fā)時間將會被大幅地縮短误趴。也就是說霹琼,發(fā)送端主機,在發(fā)送了一個段以后不必要一直等待確認(rèn)應(yīng)答,而是繼續(xù)發(fā)送枣申,類似于并行發(fā)送售葡。如下圖所示:
在接收到所有的 ACK 之后(其實并不是每一個報文段都會回復(fù) ACK,可能會對兩個報文段發(fā)送一個 ACK忠藤,也可能會對多個報文段發(fā)送 1 個 ACK)挟伙,窗口就會指向下一個要發(fā)送的數(shù)據(jù)區(qū)間,直至數(shù)據(jù)發(fā)送完畢模孩。其實即使這樣傳輸數(shù)據(jù)像寒,在傳輸大量數(shù)據(jù),如上傳大文件的時候,如果客戶端與服務(wù)端的網(wǎng)絡(luò)延遲較高隐锭,還是會產(chǎn)生較大的耗時揣钦,因為即使并行傳輸,但并行的量是受限的筷笨,并行批次之間的網(wǎng)絡(luò)延遲還是無法忽略的。
滑動窗口文字描述起來還是很費勁龟劲,可以看B站的動畫胃夏,非常形象。
慢啟動
服務(wù)器發(fā)送數(shù)據(jù)包昌跌,當(dāng)然越快越好仰禀,最好一次性全發(fā)出去。但是蚕愤,發(fā)得太快答恶,就有可能丟包。帶寬小萍诱、路由器過熱悬嗓、緩存溢出等許多因素都會導(dǎo)致丟包。線路不好的話裕坊,發(fā)得越快包竹,丟得越多。
最理想的狀態(tài)是籍凝,在線路允許的情況下周瞎,達(dá)到最高速率。但是我們怎么知道饵蒂,對方線路的理想速率是多少呢声诸?答案就是慢慢試。
TCP 協(xié)議為了做到效率與可靠性的統(tǒng)一苹享,設(shè)計了一個慢啟動(slow start)機制双絮。開始的時候浴麻,發(fā)送得較慢,然后根據(jù)丟包的情況囤攀,調(diào)整速率:如果不丟包软免,就加快發(fā)送速度;如果丟包焚挠,就降低發(fā)送速度膏萧。
Linux 內(nèi)核里面設(shè)定了(常量 TCP_INIT_CWND),剛開始通信的時候蝌衔,發(fā)送方一次性發(fā)送 10個 數(shù)據(jù)包榛泛,即"發(fā)送窗口"的大小為 10。然后停下來噩斟,等待接收方的確認(rèn)曹锨,再繼續(xù)發(fā)送。
發(fā)送方通過 ACK 中的“Ack Num”及“窗口大小”剃允,再加上自己已經(jīng)發(fā)出的數(shù)據(jù)包的最新編號沛简,就會推測出接收方大概的接收速度,從而降低或增加發(fā)送速率斥废。
數(shù)據(jù)包的遺失處理
每一個數(shù)據(jù)包都帶有下一個數(shù)據(jù)包的編號椒楣。如果下一個數(shù)據(jù)包沒有收到,那么 ACK 的編號就不會發(fā)生變化牡肉。
舉例來說捧灰,現(xiàn)在收到了 4 號包,但是沒有收到5號包统锤。ACK 就會記錄毛俏,期待收到 5 號包。過了一段時間跪另,5 號包收到了拧抖,那么下一輪 ACK 會更新編號。如果 5 號包還是沒收到免绿,但是收到了 6 號包或 7 號包,那么 ACK 里面的編號不會變化擦盾,總是顯示 5 號包嘲驾。這會導(dǎo)致大量重復(fù)內(nèi)容的 ACK。
如果發(fā)送方發(fā)現(xiàn)收到三個連續(xù)的重復(fù) ACK 或者超時了還沒有收到任何 ACK迹卢,就會確認(rèn)丟包辽故,即 5 號包遺失了,從而再次發(fā)送這個包腐碱。通過這種機制誊垢,TCP 保證了不會有數(shù)據(jù)包丟失掉弛。
其他
TCP Dump
用于抓包分析,可以參照tcpdump詳細(xì)教程喂走,或者使用命令 man tcpdump 進(jìn)行查看殃饿。
常用如:sudo tcpdump -nn net 100.83 -XX -w my.cap
實例分析
這是通過 wireshark 打開的 TCP dump 文件中的一次完整請求過程。
三次握手過程
首先看第一個是數(shù)據(jù)包是客戶端發(fā)送鏈接請求 Syn=1芋肠,Seq=0
服務(wù)的收到請求后乎芳,發(fā)送 SYN+ACK,Ack=1帖池,Syn=1奈惑,Seq=0
客戶端收到 SYN+ACK 后返回 ACK,Seq=1睡汹,Ack=1肴甸。到這里三次握手過程就已經(jīng)建立完成。
數(shù)據(jù)傳輸
首先是客戶端發(fā)送請求數(shù)據(jù)囚巴,Seq=1雷滋,Next Seq=716(Seq + TCP payload)。
服務(wù)的收到請求后的 ACK文兢,Seq=1, Ack = 716晤斩。
后面的兩個數(shù)據(jù)包就是服務(wù)端向客戶端發(fā)送的結(jié)果數(shù)據(jù),與客戶端發(fā)起請求過程一致姆坚,只不過是由服務(wù)端發(fā)起澳泵。
四次揮手過程
客戶端發(fā)送斷開連接請求,F(xiàn)in=1兼呵,Seq=716
服務(wù)端發(fā)送 ACK+FIN兔辅,F(xiàn)in=1,Ack=717击喂,Seq=1206
客戶端發(fā)送 ACK维苔,Ack=1207,這樣兩邊的連接就都會斷開了懂昂。
狀態(tài)編碼
下表為 TCP 狀態(tài)碼列表介时,以 S 指代服務(wù)器,C 指代客戶端凌彬,S&C 表示兩者沸柔,S/C 表示兩者之一:
狀態(tài) | 端 | 說明 |
---|---|---|
LISTEN | S | 服務(wù)器等待從任意遠(yuǎn)程 TCP 端口的連接請求。偵聽狀態(tài)铲敛。 |
SYN-SENT | C | 客戶在發(fā)送連接請求后等待匹配的連接請求褐澎。通過 connect() 函數(shù)向服務(wù)器發(fā)出一個同步(SYNC)信號后進(jìn)入此狀態(tài)。 |
SYN-RECEIVED | S | 服務(wù)器已經(jīng)收到并發(fā)送同步(SYNC)信號之后等待確認(rèn)(ACK)請求伐蒋。 |
ESTABLISHED | S&C | 服務(wù)器與客戶的連接已經(jīng)打開工三,收到的數(shù)據(jù)可以發(fā)送給用戶迁酸。數(shù)據(jù)傳輸步驟的正常情況。此時連接兩端是平等的俭正。這稱作全連接奸鬓。 |
FIN-WAIT-1 | S&C | (服務(wù)器或客戶)主動關(guān)閉端調(diào)用 close() 函數(shù)發(fā)出 FIN 請求包,表示本方的數(shù)據(jù)發(fā)送全部結(jié)束段审,等待 TCP 連接另一端的 ACK 確認(rèn)包或 FIN&ACK 請求包全蝶。 |
FIN-WAIT-2 | S&C | 主動關(guān)閉端在 FIN-WAIT-1 狀態(tài)下收到 ACK 確認(rèn)包,進(jìn)入等待遠(yuǎn)程 TCP 的連接終止請求的半關(guān)閉狀態(tài)寺枉。這時可以接收數(shù)據(jù)抑淫,但不再發(fā)送數(shù)據(jù)。 |
CLOSE-WAIT | S&C | 被動關(guān)閉端接到FIN后姥闪,就發(fā)出 ACK 以回應(yīng) FIN 請求始苇,并進(jìn)入等待本地用戶的連接終止請求的半關(guān)閉狀態(tài)。這時可以發(fā)送數(shù)據(jù)筐喳,但不再接收數(shù)據(jù)催式。 |
CLOSING | S&C | 在發(fā)出 FIN 后,又收到對方發(fā)來的 FIN 后避归,進(jìn)入等待對方對己方的連接終止(FIN)的確認(rèn)(ACK)的狀態(tài)荣月。少見。 |
LAST-ACK | S&C | 被動關(guān)閉端全部數(shù)據(jù)發(fā)送完成之后梳毙,向主動關(guān)閉端發(fā)送 FIN哺窄,進(jìn)入等待確認(rèn)包的狀態(tài)。 |
TIME-WAIT | S/C | 主動關(guān)閉端接收到 FIN 后账锹,就發(fā)送 ACK 包萌业,等待足夠時間以確保被動關(guān)閉端收到了終止請求的確認(rèn)包〖榧恚【按照 RFC 793生年,一個連接可以在 TIME-WAIT 保證最大四分鐘,即最大分段壽命(maximum segment lifetime)的 2 倍】 |
CLOSED | S&C | 完全沒有連接廓奕。 |
維基百科
Tcp的三次握手和四次揮手
為什么tcp 連接斷開只有3個包抱婉?
TCP 協(xié)議簡介
太厲害了,終于有人能把TCP/IP 協(xié)議講的明明白白了