您好,登錄后才能下訂單哦!
TCP(Transportation Control Protocol)協議與IP協議是一同產生的。事實上,兩者最初是一個協議,后來才被分拆成網絡層的IP和傳輸層的TCP。我們已經在UDP協議中介紹過,UDP協議是IP協議在傳輸層的“傀儡”,用來實現數據包形式的通信。而TCP協議則實現了“流”形式的通信。
TCP的內容非常豐富。我不能在一篇文章中將TCP講完。這一篇主要介紹TCP協議的下面幾個方面:
“流”通信的意義與實現方式
如何實現可靠傳輸
“流”通信
TCP協議是傳輸層協議,實現的是端口到端口(port)的通信。更進一步,TCP協議虛擬了文本流(byte stream)的通信。在Linux文本流中我們談到,計算機數據的本質是有序的0/1序列 (如果以byte為單位,就叫做文本流)。計算機的功能就是儲存和處理文本流。CPU + memory + 存儲設備實現了文本流在同一臺計算機內部的加工處理。通過一些IO,比如屏幕和鍵盤,文本流實現了人機交互。而進一步,如果網絡通信可在不同計算機之間進行文本流的交互,那么我們就和整個計算機系統的數據處理方式實現了對接。
IP協議(參考TCP/IP協議詳解03, 05)和UDP協議采用的是數據包的方式傳送,后發出的數據包可能早到,我們并不能保證數據到達的次序。TCP協議確保了數據到達的順序與文本流順序相符。當計算機從TCP協議的接口讀取數據時,這些數據已經是排列好順序的“流”了。比如我們有一個大文件要從本地主機發送到遠程主機,如果是按照“流”接收到的話,我們可以一邊接收,一邊將文本流存入文件系統。這樣,等到“流”接收完了,硬盤寫入操作也已經完成。如果采取UDP的傳輸方式,我們需要等到所有的數據到達后,進行排序,才能組裝成大的文件。這種情況下,我們不得不使用大量的計算機資源來存儲已經到達的數據,直到所有數據都達到了,才能開始處理。
“流”的要點是次序(order),然而實現這一點并不簡單。TCP協議是基于IP協議的,所以最終數據傳送還是以IP數據包為單位進行的。如果一個文本流很長的話,我們不可能將整個文本流放入到一個IP數據包中,那樣有可能會超過MTU。所以,TCP協議封裝到IP包的不是整個文本流,而是TCP協議所規定的片段(segment)。與之前的一個IP或者UDP數據包類似,一個TCP片段同樣分為頭部(header)和數據(payload)兩部分 (“片段”這個名字更多是起提醒作用:嘿,這里并不是完整的文本流)。整個文本流按照次序被分成小段,而每一段被放入TCP片段的數據部分。一個TCP片段封裝成的IP包不超過整個IP接力路徑上的最小MTU,從而避免令人痛苦的碎片化(fragmentation)。
(給文本流分段是在發送主機完成的,而碎片化是在網絡中的路由器完成的。路由器要處理許多路的通信,所以相當繁忙。文本流提前在發送主機分好段,可以避免在路由器上執行碎片化,可大大減小網絡負擔)
片段與編號
TCP片段的頭部(header)會存有該片段的序號(sequence number)。這樣,接收的計算機就可以知道接收到的片段在原文本流中的順序了,也可以知道自己下一步需要接收哪個片段以形成流。比如已經接收到了片段1,片段2,片段3,那么接收主機就開始期待片段4。如果接收到不符合順序的數據包(比如片段8),接收方的TCP模塊可以拒絕接收,從而保證呈現給接收主機的信息是符合次序的“流”。
可靠性
片段編號這個初步的想法并不能解決我們所有的問題。IP協議是不可靠的,所以IP數據包可能在傳輸過程中發生錯誤或者丟失。而IP傳輸是”Best Effort” 式的,如果發生異常情況,我們的IP數據包就會被輕易的丟棄掉。另一方面,如果亂序(out-of-order)片段到達,根據我們上面說的,接收主機不會接收。這樣,錯誤片段、丟失片段和被拒片段的聯手破壞之下,接收主機只可能收到一個充滿“漏洞”的文本流。
請補上漏洞
TCP的補救方法是,在每收到一個正確的、符合次序的片段之后,就向發送方(也就是連接的另一段)發送一個特殊的TCP片段,用來知會(ACK,acknowledge)發送方:我已經收到那個片段了。這個特殊的TCP片段叫做ACK回復。如果一個片段序號為L,對應ACK回復有回復號L+1,也就是接收方期待接收的下一個發送片段的序號。如果發送方在一定時間等待之后,還是沒有收到ACK回復,那么它推斷之前發送的片段一定發生了異常。發送方會重復發送(retransmit)那個出現異常的片段,等待ACK回復,如果還沒有收到,那么再重復發送原片段… 直到收到該片段對應的ACK回復(回復號為L+1的ACK)。
終于收到ACK的發送主機
當發送方收到ACK回復時,它看到里面的回復號為L+1,也就是發送方下一個應該發送的TCP片段序號。發送方推斷出之前的片段已經被正確的接收,隨后發出L+1號片段。ACK回復也有可能丟失。對于發送方來說,這和接收方拒絕發送ACK回復是一樣的。發送方會重復發送,而接收方接收到已知會過的片段,推斷出ACK回復丟失,會重新發送ACK回復。
通過ACK回復和重新發送機制,TCP協議將片段傳輸變得可靠。盡管底盤是不可靠的IP協議,但TCP協議以一種“不放棄的精神”,不斷嘗試,最終成功。(技術也可以很勵志)
面對“挫折”,TCP協議的態度: never give up
TCP協議和UDP協議走了兩個極端。TCP協議復雜但可靠,UDP協議輕便但不可靠。在處理異常的時候,TCP極端負責,而UDP一副無所謂的樣子。我們可以順便“黑”一下UDP協議:
同樣面對“挫折”,UDP的態度: who cares…
滑窗
上面的工作方式中,發送方保持發送->等待ACK->發送->等待ACK…的單線工作方式,這樣的工作方式叫做stop-and-wait。stop-and-wait雖然實現了TCP通信的可靠性,但同時犧牲了網絡通信的效率。在等待ACK的時間段內,我們的網絡都處于閑置(idle)狀態。我們希望有一種方式,可以同時發送出多個片段。然而如果同時發出多個片段,那么由于IP包傳送是無次序的,有可能會生成亂序片段(out-of-order),也就是后發出的片段先到達。在stop-and-wait的工作方式下,亂序片段完全被拒絕,這也很不效率。畢竟,亂序片段只是提前到達的片段。我們可以在緩存中先存放它,等到它之前的片段補充完畢,再將它綴在后面。然而,如果一個亂序片段實在是太過提前(太“亂”了),該片段將長時間占用緩存。我們需要一種折中的方法來解決該問題:利用緩存保留一些“不那么亂”的片段,期望能在段時間內補充上之前的片段(暫不處理,但發送相應的ACK);對于“亂”的比較厲害的片段,則將它們拒絕(不處理,也不發送對應的ACK)。
總有那么幾個“出格”片段
滑窗(sliding window)被同時應用于接收方和發送方,以解決以上問題。發送方和接收方各有一個滑窗。當片段位于滑窗中時,表示TCP正在處理該片段。滑窗中可以有多個片段,也就是可以同時處理多個片段。滑窗越大,越大的滑窗同時處理的片段數目越多(當然,計算機也必須分配出更多的緩存供滑窗使用)。
同時處理多個片段
我們假設一個可以容納三個片段的滑窗,并假設片段從左向右排列。對于發送方來說,滑窗的左側為已發送并已ACK過的片段序列,滑窗右側是尚未發送的片段序列。滑窗中的片段(比如片段5,6,7)被發送出去,并等待相應的ACK。如果收到片段5的ACK,滑窗將向右移動。這樣,新的片段從右側進入滑窗內,被發送出去,并進入等待狀態。在接收到片段5的ACK之前,滑窗不會移動,即使已經收到了片段6和7的ACK。這樣,就保證了滑窗左側的序列是已經發送的、接收到ACK的、符合順序的片段序列。
對于接收方來說,滑窗的左側是已經正確收到并ACK回復過的片段(比如片段1,2,3,4),也就是正確接收到的文本流。滑窗中是期望接收的片段(比如片段5, 6, 7)。同樣,如果片段6,7先到達,那么滑窗不會移動。如果片段5先到達,那么滑窗會向右移動,以等待接收新的片段。如果出現滑窗之外的片段,比如片段9,那么滑窗將拒絕接收。
下面一個視頻中,我嘗試模擬可容納三個片段的滑窗(固定大小)的工作過程。
http://v.youku.com/v_show/id_XNDg1NDUyMDUy.html
上面的視頻是我用Python和matplotlib包制作的。藍色點表示片段,紅色點表示ACK。為了說明亂序片段,我故意讓片段和ACK的速度從兩個值中隨機選擇。
可以看到,隨著滑窗的滑動,越來越多的片段被正確的傳送。利用滑窗,我們一定程度上實現了對亂序數據的緩存。但是,過于亂序的數據依然會被拒絕。我們之前說的stop-and-wait的工作方式,相當于發送方和接收方的滑窗都只能容納一個片段。
我們將在以后看到,TCP協議有實時調整滑窗大小的算法,以實現最優效率。
總結
TCP協議和UDP協議走了兩個極端。TCP協議復雜但可靠,UDP協議輕便但不可靠。在處理異常的時候,TCP極端負責,而UDP一副無所謂的樣子。在TCP中,分段和編號實現了次序;ACK和重新發送實現了可靠性;sliding window則讓上面的機制更加有效率的運行。Never give up,這就是TCP協議的態度。
轉載:http://www.code123.cc/1282.html
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。