本文在我的技術博客同步發佈,詳情可用力戳
亦可掃描屏幕右側二維碼關注我的公衆號,公衆號內有我的聯繫方式,等你來撩...java
前幾天發了一個朋友圈,發現暗戀已久的女生給我點了個贊,因而我當晚展轉反側、徹夜未眠!想着妹子是否是對我有感受呢?否則怎麼會忽然給我點贊呢?要不趁機表個白?web
因而次日我在心中模擬了屢次表白的話語,連呼吸都反覆練習。到了晚上,我撥通了妹子的微信語音,還沒等對方開口我就按捺不住心裏的想法,開始自說自話,一陣狂亂的表達...足足五分鐘一鼓作氣,一切都是那麼天然!緩存
但是在我說完以後卻半天都沒有等到妹子的迴應...過了好一下子才聽到對方的聲音:「喂!喂!我這邊信號很差,你剛剛在說啥我一句都沒聽到,我在跟我男友逛街呢...」。服務器
我掛斷了電話,我也對我此次失敗的表白進行了深度的總結!緣由就是由於我沒有學好TCP!微信
若是我懂TCP,那我在表白以前至少要先問一句「在嗎?」!先創建可靠的鏈接,確保鏈接正常才能開始表白!網絡
若是我懂TCP,那我在我說話的過程當中須要對方不斷的確認,這樣才能保證我說的每一句話對方都能聽到!這樣我才能表白成功!tcp
因此一切都是由於我沒有學好TCP,因而我走進了圖書館...post
咱們先來看下TCP的定義:大數據
TCP全稱爲Transmission Control Protocol(傳輸控制協議),是一種面向鏈接的、可靠的、基於字節流的傳輸層通訊協議。TCP是爲了在不可靠的互聯網絡上提供可靠的端到端字節流而專門設計的一個傳輸協議。網站
這裏面每個字咱們都認識,可是連在一塊就不是那麼好理解了!那咱們就提煉一些關鍵的詞,也就是我上面高亮的那些:面向鏈接、可靠、基於字節流、傳輸層、協議、端到端!理解了這些關鍵字也就理解了TCP的實現原理,那咱們就來從這些關鍵字開始進行分析!
咱們先講傳輸層,由於能夠從比較高的層面去看TCP,咱們先看下經典的OSI七層網絡參考模型:
當咱們須要在網絡上進行數據交換的時候,就須要通過這麼幾層。每一層都有相關落地的實現,咱們今天要講的TCP就是傳輸層的一種落地實現。可能咱們平時在說到傳輸層的時候天然而然的就想到的TCP,可是TCP只是傳輸層的一種實現,其餘比較常見的傳輸層協議還有UDP等!
我知道乾巴巴的文字對你來講太抽象,那我就抓個包來看看,讓這幾層更加具象!本文中全部的包都是經過postman發送請求,而後用wireShark來抓的!若是對這兩款軟件還不瞭解的盆友能夠先去了解下哈,這裏不過多說明。咱們在postman中輸入www.17coding.info的域名,而後發送請求,wireshark就能抓到數據包了。
圖上已經標明每一層與抓到的數據包對應的關係了!咦!咱們上面不是說的7層網絡參考模型麼?爲何數據包只有5層呢?注意參考二字,7層模型是一個理論模型,實際的網絡中每每都把應用層、會話層、表示層統爲應用層!
說到協議,就是雙方共同遵照的一種約定!好比我寫的這篇文章裏,你可以看懂我寫的每個字並明白個人意思,那就是由於咱們都遵循了漢語的語法,這自己也就是一種協議。還有好比咱們寫代碼就必須按照規定的語法進行編寫,這樣編譯器才能進行正確編譯。
在計算機網絡中也有不少協議,好比常見的應用層協議http、ftp、dns協議等等。常見的傳輸層協議有TCP、UDP等等...其實這些協議都是發送方和接收方都在遵循的一種規範。若是咱們遵循了其規範,也能成爲協議的實現者,好比本身寫一個web服務器處理用戶請求。甚至咱們還能本身規定一套協議,供別人使用!
咱們前面說了協議的定義,那TCP協議確定也有必定的規範咯!這樣通訊雙方纔能識別對方的數據報文,進行數據交換,咱們先看下TCP的報文格式
TCP報文包含數據頭和數據體,頭部有5行的固定長度以及1行可變長度!圖上前面5行就是固定長度!固定長度的每一行佔有4個字節(32位)。所以頭部固定長度就爲5*4=20個字節!
到這裏咱們能夠抓個包來看下加深印象,咱們依然向www.17coding.info發送一個請求,而後看看其TCP部分的數據包
接下來那咱們就一行一行的來分析TCP的頭部:
第一行:
一、源端口:發送方端口
二、目標端口:接收方端口
前面咱們說到TCP是端到端的,這裏就能很好的體現了!每一個數據包中都有發送方和接收方的端口。這裏每一個端口占用2個字節(16位)。
第二行、第三行:
一、序號:tcp是面向字節流的,數據分塊在緩存存放及發送,序號用來標記某個數據包最開始的字節是整個數據的第多少個字節。
二、確認號:每次收到請求後,接收方都會回覆發送方,告訴對方本身已經接收了多少字節,下一個數據包須要從第多少字節開始發送。這裏的值通常等於接收到的序號+接收到的數據包數據部分長度。
這裏的序號和確認號是保證TCP可靠特性所不可或缺的,咱們後面會經過抓包來詳細分析!序號和確認號分別都佔用了4個字節(32位)!
第四行:
一、數據偏移:這裏叫頭部長度更爲合適。前面說過TCP頭部長度有部分是可變的,因此須要標識數據包數據部分從哪裏開始。這個值佔用了4位。
二、保留:未使用,供擴展使用。這個值佔了3位。
三、標誌:標誌一共有9個,每一個標識佔1位,共佔9位。上面的抓包截圖就能看到這9個標識位!
3.一、NS:Nonce,與ECN顯式擁塞通知相關。
3.二、CWR:CWR 標誌與後面的 ECE 標誌都用於 IP 首部的 ECN 字段,ECE 標誌爲 1 時,則通知對方已將擁塞窗口縮小
3.三、ECE:ECN-Echo,若設置了該標識,則會通知對方,從對方到這邊的網絡有阻塞。
3.四、URG:Urgent,用於在發送方加塞。好比在下載文件的時候,下到一半了須要中止下載,就須要發送一個緊急的請求告訴對方中止發送數據。數據包不排隊。
3.五、ACK:Acknowledgment,標記爲一個確認。
3.六、PSH:Push,與URG對應的,用於接收方加塞。
3.七、RST:Reset,表示出現嚴重差錯,可能須要從新建立TCP鏈接。若是咱們打開某個網站一直沒刷出來,咱們F5進行刷新,那以前的數據包就要拒絕。
3.八、SYN:用於同步,創建請求的時候用。在握手時候會帶這個標記!
3.九、FIN:通訊結束,釋放鏈接的時候用。在揮手時候會帶這個標記!
四、窗口:無論是發送方仍是接收方,都有對應的發送窗口和接收窗口。在通訊以前,通訊雙方會協商窗口的大小。發送方按照接收方的接收窗口設置本身的發送窗口,同時發送窗口還受擁塞窗口的限制,這個在擁塞控制部分會提到!在發送過程當中窗口會根據接收方的處理能力調整。這個值對TCP的可靠傳輸及流量控制起了很大的做用!這個值佔了16位。
第五行:
一、校驗和:用於校驗數據包是否完整或者被修改。這個值佔了16位。
二、緊急指針:用來標記本報文段中緊急數據的指針,也就是指明瞭從數據包數據部分的頭部到指定位置的數據爲緊急數據,只有在設置了標誌位URG的時候才起做用。這個值佔了16位。
第六行:
一、選項:選項裏面也有些重要的數據,咱們挑幾個講一下
1.一、MSS:MSS的全稱爲Maximum segment size,雙方協商的每個報文段所能承載的最大數據長度(不包括文段頭)。
1.二、WS:WS的全稱爲Window scale,也叫窗口因子!是用來調整窗口大小的。前面咱們說到過窗口大小的字段,那這個窗口因子又是作什麼用的呢?早期的網絡帶寬、硬件配置都比較差,因此窗口大小最大隻預留了16個bit,也就是最大能設置的值爲65535。隨着硬件和網絡的發展,65535已經不能知足。因此就增長了一個WS的選項來擴展!若是設置了WS,那實際的窗口大小就等於窗口大小乘以窗口因子。
1.三、SACK:SACK的全稱爲Selective ACK,選擇性確認是創建在累計確認(後面講) 的基礎上的!只有收到失序的分組時纔會可能會發送SACK,若是接收方接收到了後面的數據包,而發現前面的數據包丟失,則會通知發送方哪些報文段丟失,須要重發!
二、填充:這個字段是爲了讓整個頭部爲4個字節的倍數。java中也有不少相似的用法!
咱們找到一個數據包,看看其詳細的頭部數據:
一、紅色部分顯示了TCP頭部的長度爲32byte,以及選項部分爲12byte。前面咱們說了TCP首部固定長度爲20byte,因此20+12=32。
二、黃線部分的窗口大小爲259byte,窗口因子爲256。因此實際的窗口大小爲259*256=66304!
從我表白失敗的例子就能看到,我還未確保鏈接的正常就開始表白,致使我說完了對方卻由於信號很差沒有聽到。若是我事先確保鏈接正常,就不會出現這樣的狀況了!咱們前面說了TCP是面向鏈接的,那TCP是怎麼面向鏈接的呢?
沒錯,都是從握手開始!咱們都知道,tcp創建鏈接須要通過三次握手,那每次握手都交代了什麼呢?若是隻進行兩次握手行不行?咱們先看一個電話接通的場景:
A:你好,你能聽到嗎?
B:我能聽到,你能聽到嗎?
A:我也能聽到。
.......
在正式通話以前,爲了確保通話的可靠,每每都須要通過上面的三次對話進行確認。那這三次對話是必須的嗎?每一次對話的必要性又是什麼呢?
A:你好,你能聽到嗎?(讓B知道A能說話)
B:我能聽到,你能聽到嗎?(讓A知道B能聽到,且能說話)
A:我也能聽到。(讓B知道A能聽到)
.......
只有通過三次的對話,才能確認本身的聲音能被對方聽到且能聽到對方的聲音。這也才能開展後續的對話。這裏咱們就不得不祭出經典的三次握手圖了:
咱們分析三次握手過程及每次握手後的狀態以下:
一、A主機發送標識SYN=1(SYN表示A請求跟B創建鏈接,前面在講TCP頭部時候有說到過),序號Seq=x,第一次握手請求發送後A的狀態爲SYN_SENT,B在接收到請求後狀態由LISTEN變爲SYN_RCVD!
二、B主機收到鏈接請求後向A主機發送標識SYN=1,ACK=1(SYN表示B請求跟A創建鏈接,ACK表示對A的鏈接請求進行應答),序號Seq=y,確認號Ack=(x+1),A接收到B的確認後,狀態變爲ESTABLISHED,B的狀態依然爲SYN_RCVD!
三、主機A收到後檢查Ack是否正確,若正確,則發送標識ACK=1(表示對B的鏈接請求進行應答),序號Seq=(x+1),確認號Ack=(y+1)。B接收到A的確認後,A和B的狀態都變爲ESTABLISHED!
這裏咱們要注意的幾點是:
一、圖中的發送請求中中括號裏面的SYN、ACK就是前面說TCP頭部中的那幾個標誌位!而Seq和Ack分別表明序號和確認號。
二、接收方在接收到發送方發送的Seq後,應答一個Ack,Ack的值等於Seq+1,表示已要發送方開始發送Seq+1位置的數據。
二、B在接收到了A的鏈接請求後回覆中同時發送了SYN、ACK兩個標識位,將創建鏈接的請求和對A的應答在同一個包中發送了,這也是爲何只須要三次握手,就能創建鏈接。
咱們依然向www.17coding.info發送請求,下面爲三次握手的包:
在info那一欄,咱們很明顯的能看到發送的數據包頭部有咱們上面說到的那些標誌位,還有Seq、Ack等頭部信息,還有Win、MSS等頭部選項數據!所以三次握手不只僅是單純創建鏈接,還會協商一些參數!
當我鼠標選擇某一行時,若是這個數據包包含了對某個數據包的確認(也就是有ACK的標記),就能在對應的數據包的No列上面看到一個小勾勾,好比上面圖中我鼠標選擇的是第三次握手的數據包,在第二次握手的數據包前面就有個小勾勾。
經過三次握手,雙方就創建了一個可靠的鏈接,就能進行數據的傳輸了!當數據傳輸完成,就得將鏈接關閉,由於鏈接也是一種資源!鏈接的關閉須要通過四次揮手!
爲何握手能夠三次完成,可是揮手卻須要四次呢?我偏要三次行不行?其實也沒啥不能夠的!好比下面的對話場景:
A:我說完了,你說完就掛電話吧!
B:好嘞,我也說完了,能夠掛電話了!
A:好嘞,拜拜。
掛斷......
這樣三次對話就能夠實現揮手了,可是在實際的網絡中,當我發出一個請求的時候,可能服務器的響應體比較大,須要較長時間的傳輸!因此當客戶端主動發起斷開請求的時候,服務器先回應一個確認,等全部數據傳輸完畢後再發送服務器斷開的請求。
A:我說完了,你說完就掛電話吧!
B:好嘞...
B:......
B:我也說完了,能夠掛電話了
A:好嘞,拜拜
掛斷......
因此大部分狀況下都須要進行四次揮手!可是,在我我的的抓包實踐中,也會有三次揮手就能完成斷開鏈接的狀況。
這裏咱們又不得不祭出經典的四次揮手圖了:
咱們分析四次揮手過程及每次揮手後的狀態以下:
一、主機A發送標識FIN=1(FIN表示A請求關閉鏈接)用來關閉A到B的數據傳輸。此時A的狀態爲FIN_WAIT_1!
二、主機B收到關閉請求後向A發送ACK(ACK表示應答A的關閉鏈接請求),A再也不向B發送數據。此時A的狀態爲FIN_WAIT_2,B爲CLOSE_WAIT!
三、主機B發送標識FIN=1用來關閉B到A的數據傳輸。此時A的狀態爲TIME_WAIT,B爲LAST_ACK!
四、主機A收到關閉請求後向B發送ACK,此時B再也不向A發送數據。此時A、B都關閉了,狀態變爲CLOSED。
在圖中咱們能看到,A的TIME_WAIT狀態會持續2MSL再變成CLOSED,MSL(Maximum Segment Lifetime)的中文能夠譯爲「報文最大生存時間」!他是任何報文在網絡上存在的最長時間,超過這個時間報文將被丟棄。那TIME_WAIT維持2MSL的做用是什麼呢?
一、第4次揮手的時候主機A發送ACK到主機B,若是發送完成後就直接就關閉鏈接,那若是因爲網絡緣由B沒有收到ACK,那B就無法關閉鏈接了!所以A在回覆確認後,還須要等待,萬一B沒有收到應答還會繼續發送FIN的請求。
二、若是不等待2MSL,那客戶端的端口可能會被重用,若是再次用這個端口創建與服務器的鏈接,那先後兩個使用相同四元組的的鏈接之間會造成干擾!
咱們看上面向www.17coding.info發送請求的揮手數據包:
可能你們在抓包的時候不能立馬看到四次揮手的數據包!那是由於在HTTP1.1及以後,默認都開啓了長鏈接!也就是在一次請求以後,創建的鏈接並不會立馬關閉,而是供後續的其餘請求繼續使用,以減小每次從新創建鏈接的資源消耗!若是想發出請求後立馬能抓到四次揮手的數據包,能夠設置Http的頭部Connection:close
。這樣每次發送請求都能看到完整的三次握手四次揮手的過程啦!
保證傳輸的可靠咱們前面已經說到了面向鏈接,創建鏈接是保證數據傳輸的第一步。那在鏈接創建以後的數據傳輸怎麼保證可靠呢?
咱們再次回到咱們打電話的場景,通常在對話的過程當中,都是得雙方都有互動,給與對方迴應。而不是一我的一個勁的說而另外一方沒有任何迴應!好比下面場景:
A:跟你講哦,我上週網上認識了一個妹子
B:嚯,牛逼啊!
A:而後我昨天約出來見面了
B:666啊!而後呢?
A:而後咱們@#¥%……&
B:臥槽,你剛剛說啥我沒聽清,你再說一遍?...
這樣的確認和應答就確保了雙方的通訊可以完整可靠。TCP也採用了這種y應答和確認重傳的機制,保證在不可靠的網絡上實現可靠的傳輸。只要我沒有收到確認,我就認爲沒有發送成功,就會重發。
中止等待協議就是每次給對方發送數據包後,須要等待對方的迴應而後再發送下一個數據包!中止等待協議會出現以下幾種狀況:
一、無差錯狀況:A發送M1包到B,B收到後會給A一個確認,當A收到B的確認後再發送包M2。
二、超時重傳:A發送M1包到B,若是發送過程當中包丟失,A會從新發送。A等待重發的時間是比一個報文的往返時間(RTT)稍微多一點。
三、確認丟失:若是B在給A發送確認的時候丟失,A會從新發送M1包給B,因爲B已經處理過M1的數據包因此B會丟棄報文,而後重傳確認M1給A。
四、確認遲到:若是A發送數據包M1給B,B回覆確認的時候延遲了。這時A又會從新發送包M1給B,B收到後丟棄數據包,而後重傳確認M1給A。這時A會收到屢次確認,當第二次收到遲到的確認後A也會丟棄該確認。
咱們從上面能看到,中止等待協議每次都是等到收到確認後再發下一個數據包。只要我沒收到你給個人確認,我就認爲你沒有收到我發的數據包,我就會進行重發!這樣雖然可靠,可是會致使信道利用率較低!
流水線傳輸就是每次發送多組數據包,沒必要每次發完一組就停下來等待對方的確認。因爲信道上一直有數據不間斷的傳輸,所以能夠得到較高的信道利用率!
流水線傳輸如何保證可靠的呢?須要發送方維持發送窗口,假如發送窗口是5,那5個數據包會同時發送,而後等確認!若是有收到接收方的確認,窗口就會滑動,進行第6個數據包的發送。
若是都是單個確認,可能效率會比較低,因此有了累計確認!也就是說假如發送方發送了數據包一、二、三、4,接收方只須要回複對數據包4的確認,那表示1234數據包都已經收到了,就能夠進行第五個數據包的發送了!假如發送了數據包一、二、三、4,其中第三個數據包丟失,那該怎麼確認呢?TCP只會回覆對數據包2的確認,而且對數據包4進行選擇性確認(TCP頭部選項講到過的SACK),這樣發送方就知道數據包4已經成功發送,只須要重發數據包3。
繼續前面抓包的例子,接收方並非對每一個數據包都進行確認,而是對多個數據包進行累計確認:
這裏咱們能看到服務器發送多個數據包後,客戶端才進行了一次確認。
經過前面咱們知道了,經過創建可靠的鏈接和確認機制,保證了TCP的鏈接的可靠!可是每一個人使用的計算機的處理能力都是不同的,我發送太快了對方處理不過來怎麼辦呢?通訊雙方怎麼去協調發送和接收數據的頻率呢?
在介紹TCP頭部的時候,咱們已經提到過滑動窗口,而且介紹了相關的控制參數Win!也說到了接收窗口和發送窗口!那他們的關係是怎麼樣的呢?
假設如今A須要傳輸數據給B,B就先要告訴A本身的接收窗口有多大。A根據B的接收窗口設置本身的發送窗口!A的發送窗口時不能大於B的接收窗口的!在開始傳輸數據以前,初始的窗口設置以下圖:
如上圖咱們可否看到,B的接收窗口設置爲10個字節,那A的發送窗口設置不能超過10個字節!若是開始傳送數據,A會將數據封裝成多個數據包進行傳輸,以下圖
在沒有收到B的確認以前,A的窗口不會滑動,也就是說最多能發10個字節的數據。若是B接受到數據且回覆確認給了A,那A的窗口則進行滑動,以下圖:
這樣,A又能夠進行第十一、12個字節的發送啦!若是B的處理能力變弱了,也能夠通知A將發送窗口調小!這樣也也就很好的協調了雙方的接收和發送能力!這也就很好的實現了TCP的可靠傳輸和流量控制!
上面的數據包繼續發送,若是在發送過程當中,三、四、5這三個字節組成的數據包丟了,可是後面的數據卻收到了,這時候A的發送窗口會移動麼?
若是是這種狀況,A的發送窗口是不會移動的。B在接收到後面數據包的時候回覆給A的Ack會設置爲3,且在選項中設置一個SACK(在TCP頭部選項裏面有描述),告訴A哪部分數據收到了,而哪部分數據須要進行重發!
利用滑動窗口技術,能夠很好的協調雙方的收發能力。可是,網絡情況是很是複雜的,且在同一個網絡上可能有千千萬萬個發送方和接收方!若是你們都須要傳輸數據都須要佔用網絡,不作好控制措施,就會致使整個網絡會堵塞甚至癱瘓。
若是我要從深圳開車去廣州,我就會走高速。若是隻有我一我的開車,那確定能暢通無阻!可是高速公路不是我家的,你們都能通行!因此一到了節假日,你們都一擁而上,而高速的承運能力不會由於節假日而調整!這時候每每就須要交通管制、限流等措施去舒緩交通!
一、綠線表明理想情況下,若是高速公路的吞吐量爲100!當須要經過的車輛不超過100時,全部車輛都能順利經過!當須要經過的車輛超過100,那每次通行的車輛爲100,能提供的負載比較穩定。
二、紅色表明沒有任何交通管制狀況下,若是高速公路的吞吐量爲100!當須要經過的車輛不超過100時,會出現輕微的塞車現象!可是隨着車輛的增多,就會出現嚴重的阻塞,甚至癱瘓!
三、藍色表明在交通管制下,若是高速公路的吞吐量爲100!當須要經過的車輛不超過100時,會出現輕微的塞車現象!可是隨着車輛的增多,交通一直保存較高的負載,不會出現癱瘓的狀況!
網絡就比如高速公路,傳輸的數據包就比如要經過的車輛,而TCP則就更像一個交警,維護着數據傳輸的秩序!那TCP是怎麼作的呢?
發送方維持一個cwnd(擁塞窗口,注意這裏的擁塞窗口不能大於前面說到的發送窗口!),剛開始擁塞窗口設置爲1。若是發現這個包沒有丟失,則調整擁塞窗口爲2!若是又沒有丟包,則調整擁塞窗口爲4!這樣每次以2倍的速度一直增加到16!而後1七、1八、19這樣一個一個的增長,直到大小與發送窗口一致。這就是所謂的慢開始和擁塞避免,16就是慢開始門限......
有沒有得寸進尺的感受!
我就蹭蹭不進去...
...
我就進去不動...
...
我就..
若是在發送的過程當中發現有丟包現象,則會調整擁塞窗口大小爲1,而且設置新的慢開始門限爲出現擁塞時的二分之一,也就是說當擁塞窗口爲24的時候出現丟包現象,那新的慢開始門限就調整爲12!若是理解了上面的文字描述,下面的圖就不難理解了!
前面說過累計確認,還說到了選擇性確認。這個就跟快重傳有關!接收方若是發現丟包,不會等到累計確認,就通知發送方三個重複的確認通知對方從新發送丟失的包。當接收方收到三個重複的確認,則意識到數據包丟失,進行重傳!
經過下圖能看到,當出現丟包的狀況,接收方的Ack都是等於50,而SACK分別對60~89之間的字節都進行了選擇性的確認!這時候發送方也就知道50~59這部分數據丟失而進行重傳!
若是一旦發生丟包,擁塞窗口就變成1,這種方式也太傻了吧。若是能有個快速恢復的機制就行了!TCP就使用了快恢復機制!當出現丟包時,不會再次進行慢開始,而是直接轉入擁塞避免!也就是重新的慢開始門限進行加法增長!
看徹底文,咱們再回到TCP的定義,你是否是又能有更多的理解了呢?
TCP全稱爲Transmission Control Protocol(傳輸控制協議),是一種面向鏈接的、可靠的、基於字節流的傳輸層通訊協議。TCP是爲了在不可靠的互聯網絡上提供可靠的端到端字節流而專門設計的一個傳輸協議。