大話TCP協議

稀飯下雪發表於2018-01-22

TCP協議:

1、tcp協議被定義為可靠的協議,但是它是屬於傳輸層的協議,根據七層協議的定義,傳輸層的資料會先傳網路層(ip層),而ip層是盡最大努力做到交付,這裡可以理解成ip層也是不可靠的協議,那麼在ip層之上的tcp協議如何做到可靠交付呢?這裡要提到幾個處理方式:

  • 停止等待處理方式 停止等待處理方式可以用一下的例子來舉例:a通過tcp協議將資料傳送給b,而在tcp協議定義裡邊,這些資料是需要被分組傳送的,當然了這個分組情況是tcp協議自身決定的,而a每次將資料中的一小組傳送給b的時候都需要b傳送資料給a確認已經成功接收到資料,這樣a才會繼續將資料傳送給b,那麼網路有時候會出現異常導致b無法成功接受到資料或者資料出現錯誤的情況a和b怎麼處理呢?b在這裡是如果接受到錯誤資訊就直接丟棄然後不返回任何資料通知a,如果b無法接收到資料就不做處理,a在這裡的處理方式是給每次傳送資料設定一個計時器,如果超過這個時間了b還沒有傳送確認訊息,a就預設這次傳送失敗,然後再次傳送資料給b,這裡也被稱之為“超時重傳”和"自動重傳請求機制",如果收到了b的確認資訊則自動將計時器關閉。所以這裡這個計時器的時間的設定就顯得非常重要了,因為這個時間要比正常成功傳輸的時間稍微長一些,如果短了導致多次重傳,浪費網路資源,而長了就會導致效率邊長,而且這個正常成功傳輸的時間也不好確定,因為和經過哪些網路有關係。那麼問題來了,b在接收到a重傳之後如何處理的呢?首先b是先判斷是否已經有了這個資料,如果有了就則直接丟棄這個資料,如果沒有則繼續向會話層和應用層交付資料,而無論如何,都要再次傳送資料和a進行確認,因為之所以會導致這個情況就是因為a沒有收到確認資訊導致的。 通過這種方式,tcp協議就能在ip層不可靠的傳輸之上實現可靠的傳輸了。 不過這裡如果是每次要等到資料1傳送成功後才繼續傳送資料2,資料1和資料2都在同一個分組裡邊,就顯得效率太低了,那麼如何做到高效率呢?這裡採用了另一種機制,那就是流水線傳輸,就是說傳送方可以並行傳送多個資料,而不是說序列的傳送,這樣以來效率方面也就提高了。

  • 利用tcp協議如何實現流量控制?這裡所謂的流量控制指的是讓傳送方的傳送速率不要那麼快,讓接收方及時處理,採用的是滑動視窗的處理方式;以a和b傳送例子為例,a和b在建立連線的時候(三次握手的時候),b會告訴a它的快取佇列中還可以儲存多少個位元組(rwnd),而a再根據b告訴它的rwnd的值來傳輸資料,技術細節:b每次告訴a的時候首部ACK的值為1,小寫ack的值是確認的序號,而這裡a傳輸資料的時候會帶上seq,即傳輸資料的開始部分,如果b告訴它的rwnd是100,則此刻a傳輸的資料是(1~100)。而等b告訴a的rwnd為0的時候證明b這邊不允許傳送方再傳送資料了。而如果b又可以接收資料了,就會再次傳送一個rwnd的值告訴a,那麼問題來了,如果傳輸的時候b的網路不好,導致rwnd的值丟失了怎麼辦?解決方案是:a在接收到b的零視窗的時候(rwnd=0),a會激發一個計時器,每隔一段制定的時間傳送一個零視窗探測器,而b在接收到這個零視窗探測資訊的時候就會給出目前的視窗值,從而解除了互相等待的狀態。


tip: 這裡保持一個問題,剛剛提到的滑動視窗的傳輸方式其實是針對位元組實現的,那麼問題來了?tcp傳輸是資料包的形式實現的,那麼為何此處是根據位元組實現的呢?


  • 更詳細的三次握手流程:這裡以a傳送方和b接收方為例,b作為接收方會先開啟監聽狀態,監聽是否有傳送方做連線請求,這裡稱之為伺服器,而a作為傳送方這裡稱之為客戶端,第一次握手:a的tcp協議首部中設定SYN=1,seq=x之後將資料包傳送給b,此處SYN=1的時候規定不準攜帶資料,但是序號會自增。完成這一步操作後a的tcp狀態會變為SYN_SEND狀態。第二次握手:b在接收到a的資料的時候會根據SYN=1判斷是有客戶端連線,b在tcp資料包的首部中會將ACK=1,ack=x+1,並且再生成seq=y,將資料包傳送給a做確認,此刻b的狀態會變為SYN_RCVD狀態。第三次握手:a在接收到ACK=1的資料後通過ack=x+1明白是b的確認,a就會將ACK=1,ack=y+1,sql=x+1,再次傳送給b,之後進入ESTABLISHED狀態,在這一次的操作中允許攜帶資料,如果不攜帶則序號不會增加。

tip: 這裡將會產生一個問題,如果去掉第三次連線會怎麼樣呢?這裡給出一個例子來解答這個問題,結合以上的知識點可以知道,如果在a第一次握手的時候出現了網路滯留或者丟失問題,a的協議機制有個計時器,超時了會再次傳送請求,這也a就傳送了兩次請求連線的操作,而由於網路滯留問題,其中有一次操作在該連線結束後b才收到,那麼如果是兩次握手就結束的話,那麼b此刻就會進入established狀態,開始等待a的資料傳輸,可是a並沒有打算將資料傳輸給b,這也b就會造成等待,導致b的資源浪費。


  • 結合以上知識點來說說看更詳細的四次揮手的流程:這裡同樣以a和b進行tcp連線為例,a在要斷開和b的tcp連線的時候,a會先將頭部的FIN訊號設定為1,序號seq=u,即最後一次傳送的序號+1,然後傳送資料包,之後進入FIN-WAIT-1狀態,等待b的確認。這也是第一次握手的過程。b在收到a的資料包之後,會根據FIN為1判斷是有客戶端要進行揮手連線,b會將ACK訊號設定為1,ack=u+1,然後傳送資料包,之後就會進入CLOSE_WAIT狀態,此刻A到B的這個連線就已經斷開了,即a沒有資料要傳送了,這時TCP連線就進入了半關閉狀態,因為tcp是雙全工連線,a到b的連線斷開了,可是b到a的連線還在,而a在接收到b的資料包之後便進入了FIN-WAIT-2的狀態,等待b釋放連線,此刻便完成了二次揮手,第三次揮手是b會傳送一個FIN資料包,會再次傳送ack為u+1的資料包,此刻b便進入LAST_ACK狀態,第三次揮手結束,而第四次揮手是a在接收到b的資料包之後,會根據FIN和ack=u+1判斷是第三次揮手,之後a會傳送一個ACK資料包,然後進入TIME_WAIT 狀態,這裡有個地方要注意的,那就是此時TCP連線尚未關閉,而是要等一個計時器的時間,之後才進入CLOSE狀態,而b在接受到資料包紙後也會進入CLOSE狀態。

tip: 這裡將會產生幾個問題:

  • 問題一?為什麼在第四次揮手a要等待一個計時器的時間才進入CLOSE狀態?主要原因有:如果a的網路不好,在第四次揮手的時候傳送的資料包被丟棄掉了,這樣b就會由於沒有收到確認資訊而重發資料包,如果a即可就進入CLOSE狀態了那麼肯定無法在此刻接收到b的資料,而讓a等待一個計時器的時間就可以解決這個問題了。
  • 問題二?在a釋放tcp連線處於第二次揮手的時候a關機了,那麼b會處於什麼狀態?並且b怎麼處理這種情況?如果a關機了,那麼b會處於LAST-ACK狀態,之後會在傳送資料給a的時候無法接受到a的確認資料包,為了不讓b無限等待下去,b會啟動一個保活計時器機制,b會在通常兩個小時後還沒有接收到資料包的話傳送一個探測報文段,以後會間隔75分鐘傳送一次,如果10次之後還沒有響應,則斷開連線。
  • 問題三?伺服器tcp連線存在大量close_wait狀態,為什麼?這裡有個地方上面沒有提到,以為close_wait狀態遷移到last_ack狀態是自發的,其實不是,是要socket.close()才會過度到last_ack,那麼大量連線處於close_wait狀態證明沒有及時close掉socket,可能是io阻塞問題。

2、tcp協議中使用到的比較高效率的演算法:

  • Nagle演算法:Nagle演算法的目的主要是用來解決a和b tcp協議傳輸的資料傳送的過程,過程是:應用層的應用將要傳送的資料逐個位元組傳輸給傳輸層的tcp的傳送快取的時候,tcp先把第一個位元組傳送給接收方,把之後資料繼續快取起來,等到接收方迴應之後,傳送方再把快取的資料傳送出去,然後等到接收方迴應,再繼續傳送快取的資料給傳送方,這裡有點序列的方式。Nagle還規定:當送達的資料已經達到傳送視窗大小的一半時就立即傳送資料包。 那麼問題來了,將滑動視窗機制和這個結合,可以知道傳送方傳送資料是需要接收方告知傳送的rwnd是多少的,而Nagle演算法這裡,先將第一個位元組傳送給接收方,而如果此時接收方的快取佇列中接收到這個位元組後就滿了,而互動式的應用程式這裡是一個個從快取中讀取位元組的,如果讀取完接收方就傳送確認並告知rwnd為1,而傳送方收到確認後再次傳送一個位元組過來,接收方再次一個位元組的處理,長此以往,效率肯定是很低的,那麼如何解決呢?tcp協議是這樣處理的,接收方在rwnd少的時候會先積累下來,等到多了再去通知傳送方,而傳送方在資料包少的時候也會積累下來,等到足夠量再傳送。

相關文章