首先,爲何須要運輸層?咱們都知道網絡層中根據IP地址能夠肯定整個網絡中的一臺主機.可是,真正進行通訊的是兩臺主機中的進程.網絡層只能惟一標識主機.若是沒有了運輸層,網絡層能夠經過IP地址把數據交付給接收方,可是對於接收方主機而言,並不知道這個數據要交給哪個進程.因此在整個計算機通訊過程當中引入運輸層,站在運輸層的角度來看,互相通訊的是兩臺主機中的兩個進程互相通訊.nginx
那麼運輸層是如何肯定互相通訊的是哪一個進程?在運輸層中引入一個端口的概念.主機上的進程須要和網絡中某一臺主機進行通訊就必須監聽某一個端口,當數據到達主機時,運輸層能夠從數據報文中得知數據應該投遞到哪一個端口.這樣子監聽該進程的端口就能夠讀取到數據.算法
TCP之因此複雜,很大一部分緣由是在於它在不可靠的網絡層上創建了一個可靠的通訊.假設A主機給B主機發送數據.從網絡層的角度來看,它只負責把數據發送給主機B,至於數據可否完整到達主機B,網絡層並不關心.因此,咱們稱網絡層不提供可靠通訊.可是在這樣子不可靠的網絡層之上TCP提供了一個可靠的通訊鏈接.即發送方發送出去的數據,必須被接收方接收到.因此TCP須要作大量的工做來保證通訊的可靠性.這一點在學習TCP協議的過程當中必定要牢記,這對於理解TCP協議頗有幫助.那麼TCP是如何作到在不可靠的網絡層基礎上提供一個可靠的通訊.緩存
最簡單的保證可靠可使用應答回覆的方式:主機A發送數據給B,B收到數據以後給A發送一個回覆:"我已經收到剛剛發送的數據".這種是在理想的狀況下的通訊過程,可是也要考慮到不少意外狀況網絡
數據報沒有到達接收方,可能丟失在網絡中ssh
數據報在傳輸過程當中出現差錯,B收到差錯報文tcp
B收到正常報文以後發送給A的確認報文丟失學習
B收到正常報文以後發送給A的確認報文很長時間纔到達A優化
爲了保證在乎外狀況下主機A發送的數據仍然能夠被主機B接收到,TCP須要進行一些處理.spa
在第一種狀況下,B主機沒有收到數據報因此不會對A主機發出應答響應.A在超過一段時間仍然沒有收到主機B的確認,就認爲剛剛發送的數據報丟失了.因而從新發送剛纔的數據報.這種機制稱之爲超時重傳.計算機網絡
第二種狀況,B主機收到的報文在網絡傳輸過程當中出現差錯,與A發送的數據報不徹底同樣.那麼B就會丟失掉這份數據報,同時也不會對A發送任何通知.這樣子回到了第一種的狀況,A會認爲剛剛的數據報丟失,會對剛剛的數據報進行超時重傳.
從上面的兩種狀況咱們能夠得知A應該維護一個超時定時器.在超時定時器到期以後尚未收到接收方的確認,就認爲數據報丟失,重傳數據報.
第三種狀況,因爲B的確認丟失,A沒有收到確認在超時以後就會從新發送數據報.B在收到數據報以後應該採起兩個動做:
丟棄這個重複的數據報
向A發送這個數據報的確認.
第四種狀況,B的確認號在超時定時器到期以後纔到達A.此時A已經從新發送覺得丟失的數據報,這種狀況下A和B的處理方式分別爲:
A丟棄該確認號不作任何處理
B會再次收到A從新發送的數據,而後再次向A發送確認號
要實現上面的處理方式,發送發和接收方應該維護有以下的信息:
發送方在發送數據報以後應該保留這個數據報,防止可能的重傳
每一個數據報應該有一個惟一的標識,這樣子B才能對收到的數據報進行確認響應.
超時定時器的時間應該比數據報傳輸的平均時間長.若是太短接收方要重傳大部分沒必要要的數據報,過長則大部分時間都浪費在等待中.
以上的這種通訊方式稱之爲自動重傳請求(ARQ)既重傳的請求是自動進行的,接收方不須要告知發送方要從新發送某個數據報.正是基於以上的確認和重傳機制,能夠在不可靠的網絡層上創建可靠的TCP協議.
這種通訊方式簡單,可是也存在一個嚴重的問題:對信道的利用率過低.發送方每次都得等待接收方的確認以後才能發送下一個數據報.
爲了提升信道的利用率,發送方通常不會使用中止等待協議,而是採用相似流水線的方式發送數據報,相似下圖的方式.
從上圖咱們能夠很明顯看出來,採用流水線的方式發送能夠極大的提升信道的利用率.可是效率提升了,TCP仍然須要保證通訊的可靠性,因此當採用流水線的方式發送數據報以後,採用什麼協議來保證通訊的可靠性?
如上圖,發送方對數據分組以後不是一個一個的發送給接收方而且等待接收方的應答,而是維護了一個發送窗口,而且規定
發送窗口從左向右移動
在這個發送窗口以前的數據必然是已經發送並且等到接收方的確認收到
落在發送窗口以內的數據是接收方能夠發送的數據
在發送窗口以後的數據是不能發送的數據.
從上圖的例子中 : [1,2]數據包爲已經發送而且獲得確認的數據,[3,7]是發送方能夠發送的數據,[8,~]的數據是不能發送的數據.這種維護一個發送窗口的方式發送數據報文的行爲,稱之爲滑動窗口協議
那麼,在在滑動窗口協議下,接收方如何給發送方發送確認報文(ACK).這裏主要有兩種方式
回退N
選擇重傳
這裏,咱們先討論回退N的方式 : 這種方式很簡單,接收方使用累計確認的方式發送確認報文.接收方不會對每一個報文都發送確認,而是收到幾個分組以後,對按序到達的最後一個分組發送確認.這就意味着在此編號以前的全部分組都已經收到了.上圖,假設發送方發送了編號爲[3,7]的報文,5號報文丟失在網絡中,並且其餘的報文接收方都接收到了,這個時候接收方發送當前最高編號4給發送方.那麼,發送方就認爲[5,7]的報文都丟失了,會從新發送這些報文,即便[6,7]號報文接收方也收到了.
根據上面的描述,咱們發如今網絡環境質量很差的時候,回退N的方式會使得數據通訊變得更加擁塞.
一樣,爲了不接收方的確認報文在網絡中丟失形成死鎖,接收方應該維護一個超時時間,當在必定的時間沒有收到確認報文,則從新發送數據報.超時時間的選擇也是有必定算法的:一個報文從發出去的時間到收到相應報文確認的時間稱之爲RTT.那麼超時時間應該收集多個RRT時間以後進行一系列的計算得出一個時間.若是超時時間太短就會致使重發大量沒必要要的數據報,太長就會增大延遲.
要明確一點,從應用層傳遞下來的數據在運輸層並不必定會立刻發送出去.一樣,接收方接收到數據以後也不必定會立刻交付給應用層.在TCP中規定,通訊雙方維護有接收緩存和發送緩存兩個緩衝區用於接收數據.當應用層的數據傳遞給運輸層以後,數據並非立刻發送出去而是進去發送緩衝中等待到適當的時機再發送.一樣接收到數據以後也是立刻交付給應用層,也是放入接收緩存中等待時機再提交給應用層.
下面討論在什麼"時機"下才會把數據交付/發送出去.
對於接收緩存而言比較簡單,只緩存兩種數據:
按序到達,可是尚未被應用程序讀取.
未按序到達的數據
對於第一種狀況,多是應用程序暫時沒空從接收緩存中讀取數據,它能夠再隨時讀取接收緩存的數據.第二種狀況,說明數據沒有按序到底,TCP不會直接交付給應用層,當數據按序到達以後TCP纔會交付給應用層.至於應用層會不會讀取數據就不必定了.
對於發送緩存而言,要考慮的問題就比較多了.要根據應用層的程序特性來決定如何發送數據.
考慮一個場景,使用ssh鏈接到遠程主機的時候.這種交互式的通訊方式意味着每次輸入的命令可能只有一個字節.可是在網絡協議中,通過TCP層和IP層的封裝,發送到網絡中的字節長達41個字節(20TCP首部+20IP首部+1個字節數據).假若客戶端每次都是發送相似的小字節數據,那麼對於通訊信道的浪費將會很是嚴重.
爲了解決這個問題,引入了一個稱爲Nagle算法.這個算法的邏輯以下: 把程序要發送的數據放入TCP發送緩存,發送方只把第一個字節的數據發送出去,當收到對第一個字節數據的確認以後,才把其餘的數據組裝成一個報文發送出去.這樣子就能夠避免網絡中充斥着許多小包數據.這個算法在計算機網絡中獲得了普遍的應用,在nginx的配置項中默認已經開啓了這個選項(tcp_nopush)
可是對於一些實時競技類的遊戲則不能使用nagle算法,由於遊戲對於網絡的延遲有很高的要求,nagle會把數據延遲發送,這樣子會形成遊戲的卡頓.不少時候遊戲都不會經常使用TCP協議,而是直接使用UDP這種無鏈接簡單的方式來保證遊戲的實時性.至於魔獸採用了TCP的方式是有必定的特殊性和優化,可是也要注意魔獸即便使用了TCP也仍然是關閉了nagle算法.
另外一個問題,當接收方處理接收緩存的數據較慢的時候會引起:糊塗窗口綜合症.假設TCP接收方的緩存已經滿了,而交互式的程序只從接收緩存中讀取1個字節,這樣接收緩存只騰出了1個字節.而後向發送發發送確認,注意確認報文長達41個字節.因爲發送方老是很迫切的但願發送數據,因此當收到確認以後發送方又只發送1個字節的有效數據,可是報文長度仍是41個字節.這樣往復下去會使得網絡效率低下.
上述的問題,有兩個方法能夠減緩
接收方接收到數據一樣立刻發送確認,可是同時對發送方宣佈窗口大小爲0.這樣接收方就暫時不會發送數據
報文到達時不立刻發送確認,直到緩存有足夠的空間.這樣就能夠避免發送方滑動窗口.可是這也存在一個問題,接收方延遲發送確認的時間不該該超過超時時間,若是過長會致使發送方誤覺得數據丟失從新發送數據.
回顧上面,主要針對兩點進行了討論
如何在不可靠的IP層基礎上實現可靠通訊
提升網絡效率
下一章將針對TCP報文,三次握手,流量控制,擁塞控制等內容進行說明.