常常在前端的面試羣中發現有人會碰到面試官去詢問tcp的握手和揮手問題,諸如你瞭解tcp嗎,解釋一下tcp的三次握手和四次揮手,我認爲若是隻是簡單的瞭解這2個問題,真的那麼有意義嗎?因此,不防試着去多瞭解一點網絡通訊的內容,記得在上家公司的時候有個老哥說過,網絡通訊其實仍是蠻重要的,畢竟咱們如今不管是工做仍是生活基本都處於互聯網之中,尤爲做爲開發者基本上天天都在和http請求打交道,so 瞭解網絡傳輸的原理仍是有必要的,下面咱們稍微深刻的來看下網絡傳輸的內容。html
OSI(Open Systems Interconncection,開放系統互聯)網絡分層前端
從上到下分別是:linux
基礎內容不作過多的講解,有須要的能夠出門右轉 ok 咱們今天主要關注的是tcp層的內容,下面的內容,若是有興趣建議你們按照步驟實際操做去看看,首先介紹一個工具wireshark,這個工具能夠幫助咱們抓到tcp以及更底層的包,下載到這裏,打開後有個download,下載本身系統能用的就好,一路安裝到所有完成,接下來咱們開始一次實戰抓包。面試
我這裏利用了百度的首頁作了一次抓包實驗,首先要設置一個過濾算法
你能夠選擇http 和 tcp 或者 tcp only 而後到控制檯中去ping 一下百度首頁的ipok 看到ip是180.97.33.108chrome
而後咱們到wireshark中設置一下查詢的ipshell
到chrome中打開百度的首頁,而後就能看到tcp的傳輸信息了 內容有點多,咱們經過這個內容來觀察一下tcp的傳輸過程先看下tcp的頭部報文結構 1.tcp協議層是不關心ip的,具體ip的定位是由ip層來決定的,可是tcp層須要肯定端口號,因此他會攜帶source 和 destination的port信息,以便能找到對應的端口號;服務器
2.sequence number 實際中使用的SEQ,也就是序號,這個序號起了很重要的做用,咱們都知道tcp和udp最大的區別在於tcp是穩定而且有序的,其中seq就能夠保證有序,當A向B發送一個數據包的時候,seq會疊加,每個傳輸方在傳送數據的時候都會帶上這個信息,另外一端能按照這個序號來排序收到信息的順序,從未保證了信息的傳遞是有序的,也能經過它來確認有沒有出現丟包的狀況;另外要注意的是當有數據須要發送的時候,seq會隨該序列號爲原點,對本身將要發送的每一個字節的數據進行編號,好比當前seq = 10,本次要發送的數據包大小是200字節,那麼實際發送的時候會更新seq=210,以便保證傳輸的數據的順序;網絡
3.acknowledge number,實際中使用的ACK,是另外一端對對方seq的一個迴應,通常會把對方給的seq+1而後下一次發包的時候帶上,這樣的話對方就知道咱們是收到前面的消息的;併發
4.windown表明的是滑動窗口,實際中用win來表示,win的大小很重要,win越大的傳輸越快,由於win的大小直接決定了某一端一次能夠同時發送多少個數據包,而不用等待對方的應答ACK回來,可是win會隨着每個數據包的發送而變小(稍後解釋);
5.reserved 是tcp傳輸很重要的角色,標誌位,響應方會根據對方給的信號執行對應的操做,好比執行斷開鏈接的時候通常都是使用FIN標誌位;
基礎內容不作過多介紹,不懂的能夠移步這裏先看下概念,後面咱們會結合實際來介紹
A:B,你好,我是A 請求創建鏈接,個人seq是0,個人win是65535,我但願本次迴應個人內容長度len爲0,我本次能接收的最大內容是1460,over; B:A,你也好,收到你的信息了,我是B,我本次的seq是0(注意,雙方的序號是獨立計算的,這裏都從0開始的),我回應你的ack是1(A的seq+1,表明我收到你seq是0的消息了),個人窗口大小是8192,我但願你迴應我本次消息的len也是0,我這邊能接收的最大回應大小是1452,over; A:好的,我收到你的迴應了,我如今給你發送的seq是1(上一次是0,此次是1),我回應你的ack是1(B的seq+1),我當前的窗口大小是25984,我但願的迴應長度是0;咱們創建好鏈接了,over;
到這裏,完整的三次握手就結束了,後面就能夠執行別的數據傳輸了,到這裏,不知道有沒有想過,爲何肯定一次鏈接須要三次握手,不是1次,也不是2次,也不是4次,
A:喂喂喂,我是A,你聽的到嗎? B:在在在,我能聽到,我是B,你能聽到我嗎? A:(聽到了,老子不想理你) B:喂喂喂?聽不聽到?我X,對面死了,我掛了。。
A:喂喂喂,我是A,你聽的到嗎? B:在在在,我能聽到,我是B,你能聽到我嗎? A:聽到了,你呢?你能聽到嗎? B:??你是智障?我不是說了我能聽到嗎,不想跟xx說話。。。
A:喂喂喂,我是A,你聽的到嗎? B:在在在,我能聽到,我是B,你能聽到我嗎? A:聽到了。咱們今天去釣魚吧。。balabala
so,就是這樣,其實不是不能更多,可是可靠的同時,還要考慮性能和時間問題,因此,目前公認的握手次數仍是三次比較合理。
咱們知道tcp的鏈接是全雙工的,A和B是能夠互相通訊的,不理解的話,能夠想一想打電話(類比,不要當真),打電話的場景就是單雙工的,由於同一時間只能一我的說話,另外一我的聽,若是2我的一塊兒說話,那誰都聽不清楚了,沒有意義,可是tcp是全雙工的,就是A 正在給 B發信息的同時,B也在給A發信息,因此當斷開的時候,必需要求雙方都得知道,若是隻有一方知道,確定不行,所以,斷開的時候,就須要下面這樣:
A:B,很差意思,我這邊須要關閉鏈接了,你準備一下?(發了一個fin信號給B,等待迴應)
B:好的A,我收到你的關閉信號了,我還有數據沒發好,你等我下(迴應A,帶回去ACK的最後一個信息,失敗能夠重發)
B:A老弟,我好了,我能夠關閉了,給你最後說一下,等下你迴應個人話,我就直接關了;
A:好的老哥,我回應你一下,你收到就關閉吧,不用理我(發完這條信息後,進入time_wait狀態)
B:(收到ack信息,直接就關閉了),此過程不產生數據的交互,不算揮手次數
A:等待2MSL(最大報文段生存時間)後,B沒東西給過來,我也關了;
到這裏4次揮手就結束了,2個問題:
握手的時候,A和B打個招呼,B能夠直接把本身的SYN信息和對A的迴應ACK信息一塊兒帶上,可是揮手的時候,A說我要斷開了,B還沒發完最後的數據,所以須要先回應一下A,我收到你的斷開的請求了,可是你要等我把最後的內容給你,因此這裏分開了2步: (1)迴應A; (2)發送本身的最後一個數據
緣由是,擔憂網絡不可靠而致使的丟包,最後一個迴應B的ACK萬一丟了怎麼辦,在這個時間內,A是能夠從新發包的,可是超過了最大等待時間的話,就算收不到也沒用了,因此就能夠關閉了。
從上面的內容,咱們簡單瞭解了三次握手和四次揮手的內容,而後也知道了一些報文字段的意義,可是網絡自己是不穩定的,也就是說中間沒法保證數據包必定會到對面,那麼tcp是如何在儘量少的時間內實現穩定和有序傳輸的?
咱們知道SYN信息中會帶上本身的seq,序號,這樣能夠保證另外一方接受到後知道如何排序,可是若是發送必須都是同步的,想象,A 給 B發送的時候,須要給B 1,2,3,4,5個包,發了1後,死等1的ack回來,再給2,死等2的ack回來,在linux下每一個tcp的timeout最大是2^5 - 1 = 63
s(默認的retrytime是5次)的時間,由於當發了一個包出去後,在必定時間內沒收到ACK迴應,爲了確認不能丟包的問題,會啓動重試機制,重試5次,它們的延遲分別是:1 秒、3 秒、7 秒、15 秒、31 秒,其中31s是前5次重試的時間1+2+4+8+16=31s,最後的32s是等待最後一次重試也超時(等待的時間是2的N次方秒),因此一共就是63s,若是一個一個等,是否是有點太恐怖了,萬一網絡環境比較差,因此爲了能在不丟包的狀況下,儘可能減小時間的損耗,引入了滑動窗口的概念,window
因爲窗口由16位bit所定義,因此接收端TCP,窗口能最大提供65535個字節的緩衝,其實這個滑動窗口主要就是作限流和緩衝用的,每個tcp傳輸中的win提供的是對方的窗口大小,當A向B發數據的時候,超過B的win長度的數據會被丟掉,同時窗口還能夠提升發送數據的效率,經過相似於併發的行爲,以下圖:
能夠看到A向B連續發了3條數據,可是迴應B的ACK沒有變,也就是說都是迴應同一個B的同一個響應,可是A本身的seq更新了3次,先是1,而後是69最後是1521,說明這三個包是連續發出去的,實際上只要當前數據包的大小不超過對方的window大小,就能夠連續發的,接着看:
這四個是B響應給A的數據包,能夠看到都是在響應A給的數據包,注意看由於A連續發了好幾條,B可能一下反應不過來,因此B會把這些信息放到緩衝區,可是放的數據越多,那麼本身的緩衝區就越小,就是經過B本身的win來體現,咱們能夠看到B的win再連續變小,說明它還沒處理好,到第4條信息的時候,咱們看到B給了一個信息叫WINDOW UPDATE 而後發現B的win變大了,這就意味着它已經處理好A以前連續發的幾個數據包了,而後就會從新更新本身的win的大小,要注意的是當tcp一端的win接近或者等於0的時候,傳輸將會中止,直到window update更新說buffer已經清空了,傳輸纔會繼續。看下面這個圖
明顯能看到ACK是連續變大的,在一次鏈接中,不可能存在說先回復ACK=3922而後再回復ACK=3913,這樣的話另外一端在收到3922的時候就認爲以前的所有接收到了,實際上3913還沒收到,要注意SeqNum和Ack是以字節數爲單位,因此ack的時候,不能跳着確認,只能確認最大的連續收到的包。那麼,考慮如下狀況,假如A給的分別是1,2,3,4到5 5個包,B這邊收到1,Ack一個2(表明收到1了),而後2丟了,三、4和5收到了,能直接ACK = 6嗎?固然不行,這樣的話tcp就是不穩定的了,考慮超時重傳的2種方案:1.timeout後只從新傳2; 2.timeout後從新給二、三、四、5;
2種方案有好有壞,第一種比較慢,第二種浪費帶寬,因此tcp引入了一種快速超時重試機制(Fast Retransmit算法),不以時間計算,而以數據作驅動從新傳送,若是包沒有連續到達,好比1到了,2沒到,3,4,5也到了,這個時候,B始終返回ACK=2,表明只確認1,而後A就知道2沒到,從新發2,可是B一旦收到2會直接ACK=6給A,這個的意思就是說2拿到後,345也收到了,直接給6就ok,以下圖:
上面說的只是一種特別簡單的方案,目前,linux2.4以後,採用了一種更先進的方式,有想了解的能夠走這裏
典型的場景是DDOS攻擊,也能夠說是tcp的SYN Flood攻擊,又叫洪水攻擊; 根據上面的分析,咱們知道tcp的握手環節是比較耗時的,當client端發起鏈接請求的時候,server端會迴應,而後等待client的最終確認信息,默認狀況下的linux會等待1到63s這樣(若是有特殊的設置,這個時間能夠到1-2min這樣),默認最長是63s以後纔會斷開,以前這段時間內屬於半鏈接的狀態,服務器不會丟棄掉這些鏈接,而是會等,試想若是有一我的忽然想你的server瞬間以內發送了幾千萬個鏈接請求,可是對服務端的響應不作理睬,這樣很容易就致使咱們正常的tcp鏈接進不去,從而出現服務拒絕的狀況,而他只須要一個簡簡單單的腳本去給你丟包就能夠了,這種狀況就會致使服務器對正常的客戶端表現爲宕機。。此種攻擊的成本比較低,可是防禦卻特別麻煩,由於你必需要保證正常的不能由於訪問次數的提升而出現拒絕。
另一個沒有這個狀況嚴重的攻擊是ACK Flood攻擊,有興趣的能夠自行去查看。