作為前端的你瞭解多少tcp的內容

Guokai發表於2018-12-05

關於tcp你知道多少

經常在前端的面試群中發現有人會碰到面試官去詢問tcp的握手和揮手問題,諸如你瞭解tcp嗎,解釋一下tcp的三次握手和四次揮手,我認為如果只是簡單的瞭解這2個問題,真的那麼有意義嗎?所以,不防試著去多瞭解一點網路通訊的內容,記得在上家公司的時候有個老哥說過,網路通訊其實還是蠻重要的,畢竟我們現在無論是工作還是生活基本都處於網際網路之中,尤其作為開發者基本上每天都在和http請求打交道,so 瞭解網路傳輸的原理還是有必要的,下面我們稍微深入的來看下網路傳輸的內容。

base

OSI(Open Systems Interconncection,開放系統互聯)網路分層

從上到下分別是:

7.應用層(Application)
6.表示層(Presentation)
5.會話層(Session)
4.傳輸層(Transport)
3.網路層(Network) -- 路由器
2.資料鏈路層(Data Link) -- 交換機
1.物理層(Physical) -- 網路卡、集線器(Hub)

基礎內容不做過多的講解,有需要的可以出門右轉 ok 我們今天主要關注的是tcp層的內容,下面的內容,如果有興趣建議大家按照步驟實際操作去看看,首先介紹一個工具wireshark,這個工具可以幫助我們抓到tcp以及更底層的包,下載到這裡,開啟後有個download,下載自己系統能用的就好,一路安裝到全部完成,接下來我們開始一次實戰抓包。

抓包

我這裡利用了百度的首頁做了一次抓包實驗,首先要設定一個過濾

作為前端的你瞭解多少tcp的內容
你可以選擇http 和 tcp 或者 tcp only

作為前端的你瞭解多少tcp的內容
然後到控制檯中去ping 一下百度首頁的ip

作為前端的你瞭解多少tcp的內容

ok 看到ip是180.97.33.108

然後我們到wireshark中設定一下查詢的ip

作為前端的你瞭解多少tcp的內容
到chrome中開啟百度的首頁,然後就能看到tcp的傳輸資訊了

作為前端的你瞭解多少tcp的內容
內容有點多,我們通過這個內容來觀察一下tcp的傳輸過程

tcp base

先看下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標誌位;

基礎內容不做過多介紹,不懂的可以移步這裡先看下概念,後面我們會結合實際來介紹

三次握手(敲黑板)

作為前端的你瞭解多少tcp的內容
觀察前幾次傳輸,TLS型別的我們可以先忽略,它是處理ssl加密的內容,有興趣的可以自己去google,重點看前三次的tcp,第一次是我們自己的ip向百度的伺服器ip發了第一個包,seq=0,起始的資料訊號是0,win=65535代表我這邊的視窗大小是65535,len=0代表我這邊希望接受的包的大小長度是0,mss代表我這邊本次傳輸能接收的最大包的內容是1460(其餘的我們暫時不用關心)。下面我們模擬一下對話內容:

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次,

首先,1次肯定不行,1次的話 一方無法確認另一方的情況,所以最少都是2次起步,

2次:

A:喂喂喂,我是A,你聽的到嗎? B:在在在,我能聽到,我是B,你能聽到我嗎? A:(聽到了,老子不想理你) B:喂喂喂?聽不聽到?我X,對面死了,我掛了。。

4次:

A:喂喂喂,我是A,你聽的到嗎? B:在在在,我能聽到,我是B,你能聽到我嗎? A:聽到了,你呢?你能聽到嗎? B:??你是智障?我不是說了我能聽到嗎,不想跟xx說話。。。

所以最合理的還是3次:

A:喂喂喂,我是A,你聽的到嗎? B:在在在,我能聽到,我是B,你能聽到我嗎? A:聽到了。我們今天去釣魚吧。。balabala

so,就是這樣,其實不是不能更多,但是可靠的同時,還要考慮效能和時間問題,所以,目前公認的握手次數還是三次比較合理。

四次揮手

我們知道tcp的連線是全雙工的,A和B是可以互相通訊的,不理解的話,可以想想打電話(類比,不要當真),打電話的場景就是單雙工的,因為同一時間只能一個人說話,另一個人聽,如果2個人一起說話,那誰都聽不清楚了,沒有意義,但是tcp是全雙工的,就是A 正在給 B發資訊的同時,B也在給A發資訊,所以當斷開的時候,必須要求雙方都得知道,如果只有一方知道,肯定不行,因此,斷開的時候,就需要下面這樣:

作為前端的你瞭解多少tcp的內容

A:B,不好意思,我這邊需要關閉連線了,你準備一下?(發了一個fin訊號給B,等待回應)

B:好的A,我收到你的關閉訊號了,我還有資料沒發好,你等我下(回應A,帶回去ACK的最後一個資訊,失敗可以重發)

B:A老弟,我好了,我可以關閉了,給你最後說一下,等下你回應我的話,我就直接關了;

A:好的老哥,我回應你一下,你收到就關閉吧,不用理我(發完這條資訊後,進入time_wait狀態)

B:(收到ack資訊,直接就關閉了),此過程不產生資料的互動,不算揮手次數

A:等待2MSL(最大報文段生存時間)後,B沒東西給過來,我也關了;

到這裡4次揮手就結束了,2個問題:

1.為什麼握手需要三次,而揮手卻需要四次?

握手的時候,A和B打個招呼,B可以直接把自己的SYN資訊和對A的回應ACK資訊一起帶上,但是揮手的時候,A說我要斷開了,B還沒發完最後的資料,因此需要先回應一下A,我收到你的斷開的請求了,但是你要等我把最後的內容給你,所以這裡分開了2步: (1)回應A; (2)傳送自己的最後一個資料

2.為什麼A進入TIME_WAIT需要等待最大報文段生存的時間後,才能關閉?

原因是,擔心網路不可靠而導致的丟包,最後一個回應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 = 63s(預設的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長度的資料會被丟掉,同時視窗還可以提高傳送資料的效率,通過類似於併發的行為,如下圖:

作為前端的你瞭解多少tcp的內容

可以看到A向B連續發了3條資料,但是回應B的ACK沒有變,也就是說都是回應同一個B的同一個響應,但是A自己的seq更新了3次,先是1,然後是69最後是1521,說明這三個包是連續發出去的,實際上只要當前資料包的大小不超過對方的window大小,就可以連續發的,接著看:

作為前端的你瞭解多少tcp的內容
這四個是B響應給A的資料包,可以看到都是在響應A給的資料包,注意看因為A連續發了好幾條,B可能一下反應不過來,所以B會把這些資訊放到緩衝區,但是放的資料越多,那麼自己的緩衝區就越小,就是通過B自己的win來體現,我們可以看到B的win再連續變小,說明它還沒處理好,到第4條資訊的時候,我們看到B給了一個資訊叫WINDOW UPDATE 然後發現B的win變大了,這就意味著它已經處理好A之前連續發的幾個資料包了,然後就會重新更新自己的win的大小,要注意的是當tcp一端的win接近或者等於0的時候,傳輸將會停止,直到window update更新說buffer已經清空了,傳輸才會繼續。

丟包?

看下面這個圖

作為前端的你瞭解多少tcp的內容
明顯能看到ACK是連續變大的,在一次連線中,不可能存在說先回復ACK=3922然後再回復ACK=3913,這樣的話另一端在收到3922的時候就認為之前的全部接收到了,實際上3913還沒收到,要注意SeqNum和Ack是以位元組數為單位,所以ack的時候,不能跳著確認,只能確認最大的連續收到的包。那麼,考慮以下情況,假如A給的分別是1,2,3,4到5 5個包,B這邊收到1,Ack一個2(代表收到1了),然後2丟了,3、4和5收到了,能直接ACK = 6嗎?當然不行,這樣的話tcp就是不穩定的了,考慮超時重傳的2種方案:

1.timeout後只重新傳2; 2.timeout後重新給2、3、4、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,如下圖:

作為前端的你瞭解多少tcp的內容

上面說的只是一種特別簡單的方案,目前,linux2.4之後,採用了一種更先進的方式,有想了解的可以走這裡

攻擊

典型的場景是DDOS攻擊,也可以說是tcp的SYN Flood攻擊,又叫洪水攻擊; 根據上面的分析,我們知道tcp的握手環節是比較耗時的,當client端發起連線請求的時候,server端會回應,然後等待client的最終確認資訊,預設情況下的linux會等待1到63s這樣(如果有特殊的設定,這個時間可以到1-2min這樣),預設最長是63s之後才會斷開,之前這段時間內屬於半連線的狀態,伺服器不會丟棄掉這些連線,而是會等,試想如果有一個人突然想你的server瞬間之內傳送了幾千萬個連線請求,但是對服務端的響應不做理睬,這樣很容易就導致我們正常的tcp連線進不去,從而出現服務拒絕的情況,而他只需要一個簡簡單單的指令碼去給你丟包就可以了,這種情況就會導致伺服器對正常的客戶端表現為當機。。此種攻擊的成本比較低,但是防護卻特別麻煩,因為你必須要保證正常的不能因為訪問次數的提高而出現拒絕。

另外一個沒有這個情況嚴重的攻擊是ACK Flood攻擊,有興趣的可以自行去檢視。

TCP是個巨複雜的協議,我們今天通過一次抓包瞭解了三次握手,四次揮手,滑動視窗。超時重傳機制和典型的tcp攻擊方式,希望能對各位看官對tcp的認識有一定的幫助;

聲名:以上內容都屬於個人理解和總結,如有問題,歡迎指出~

相關文章