標籤:tcp socket 流量 緩衝 網路
一、首先和UDP作比較談談TCP的特點
(1)UDP是資料報協議,每個資料報都有長度,資料報的長度和資料一起發送和接受,各個資料報的發送和接受相互獨立,互不影響。而TCP是位元組流協議,所有經TCP發送的資料沒有記錄邊界。
(2) UDP是無串連、不可靠的協議,而TCP是有串連,可靠協議。TCP的可靠性主要指經TCP發送的資料要麼準確無誤的發送到了對端,要麼發送失敗並且以合適的方式通知應用程式,也就是可靠的傳輸資料和可靠地報告錯誤,TCP協議每一次發送資料以後先暫時將資料儲存在緩衝區,直到接收到對端的ACK然後將緩衝區中已發送的資料清除或者因為等待ACK超市而重新發送,如果重新發送多次後任未成功發送,TCP將通知應用程式資料發送失敗。同時TCP為每次發送的資料進行編號,到對端接收到資料後TCP會按照編號有序上傳到應用。而UDP則既不對接收的資料進行確認,也不會對發送的資料進行編號以保證按序到達。
(3)TCP有流量控制和擁塞控制,TCP的流量控制可以隨時告知對端他自己的緩衝區可以容納多少資料,已讓對端控制資料發送速度,以免淹沒本地緩衝區,TCP使用滑動視窗來實現流量控制。而UDP對於超出緩衝區容量的資料則直接丟棄。TCP的擁塞控制是為了防止過多的資料擁入網路,造成資料鏈路的負載過大而使得整個網路的傳輸效率過低。
(4)由於TCP需要重傳、擁塞控制、流量控制等機制,所以TCP的實現在kernel內維護一個發送緩衝區,一個接受緩衝區,在TCP協議的socket fd上調用write/send等發送函數,返回成功只表明資料成功複製到了TCP的發送緩衝區,並不能表明資料成功發送到了對端。如果成功發送到TCP緩衝區的資料在發送到對端的過程中出現錯誤,TCP會在隨後的調用中通知應用程式。而UDP發送成功則是指成功發送到了鏈路層.
(5)既然TCP協議在kernel內維護了一個發送緩衝區,那麼這個緩衝區內的資料什麼時候會正真被發送呢?這是有Nagle演算法來控制的。具體的來說:
<1>.緩衝區中的資料達到MSS
<2>.設定了NODELAY選項
<3>.該包含有FIN
<4>.所有已發送的資料包均已得到確認(未設定TCP_CROCK時)
<5>.發生了逾時
(6)TCP/IP中不僅僅有nagle演算法,還有一個TCP確認延遲機制 。當Server端收到資料之後,它並不會馬上向client端發送ACK,而是會將ACK的發送延遲一段時間(假設為t),它希望在t時間內server端會向client端發送應答資料,這樣ACK就能夠和應答資料一起發送,就像是應答資料捎帶著ACK過去。這也就意味著伺服器如果只接受資料不發送資料,那麼對接收到的資料確認會延遲較長一段時間(TCP在等待資料以便和ACK一起發送),而用戶端在這段時間由於沒有接收到上一個資料包的ACK,因此根據Nagle演算法也會一直等待。 當然,TCP確認延遲並不是一直不變的,TCP串連的延遲確認時間一般初始化為最小值40ms,隨後根據串連的重傳逾時時間(RTO)、上次收到資料包與本次接收資料包的時間間隔等參數進行不斷調整。另外可以通過設定TCP_QUICKACK選項來取消確認延遲。
(7)TCP_CORK 選項:所謂的CORK就是塞子的意思,形象地理解就是用CORK將串連塞住,使得資料先不發出去,等到拔去塞子後再發出去。設定該選項後,核心會儘力把小資料包拼接成一個大的資料包(一個MTU)再發送出去,當然若一定時間後(一般為200ms,該值尚待確認),核心仍然沒有組合成一個MTU時也必鬚髮送現有的資料(不可能讓資料一直等待吧)。
然而,TCP_CORK的實現可能並那麼完美,CORK並不會將串連完全塞住。核心其實並不知道應用程式層到底什麼時候會發送第二批資料用於和第一批資料拼接以達到MTU的大小,因此核心會給出一個時間限制,在該時間內沒有拼接成一個大包(努力接近MTU)的話,核心就會無條件發送。也就是說若應用程式層程式發送小包資料的間隔不夠短時,TCP_CORK就沒有一點作用,反而失去了資料的即時性(每個小包資料都會延時一定時間再發送),實際上TCP_CROK選項一般在傳輸檔案時用的多一些,其他地方很少見用。
二、TCP流量控制的原理:
可以看出,該TCP的接收視窗為500位元組(因為第一次rwnd是以500位元組為至的),同時可以看出每次對接收資料的ack發送同時發送了rwnd,而且rwnd在即時變化,也就是所謂的滑動視窗。可以看到B向A的確認中,ack=501哪一項的rwnd=100,也就是說之前接收到的1-500位元組中1-100位元組資料已被應用程式讀取,從而空出100位元組的緩衝區,而101-500位元組的資料仍然在緩衝區。同時到ack=601的時候,101-600位元組共500位元組的內容仍然在緩衝區內,填滿了整個緩衝區,所以rwnd=0。這兒有個問題,此時A接收到B的rwnd=0,因此不會再向B發送資料,而B把所有緩衝區的資料都處理完了,在等待A新的資料到達,這會造成A和B之間的死結。TCP的解決方案是,A收到B的rwnd=0時啟動定時器,定時器時間到達後A會向B發送一個視窗探測報文段(經攜帶一位元組資料),B收到這個報文段對A確認時就發送了新的rwnd,從而避免了死結。
二、TCP的擁塞控制:
(1)慢開始和擁塞避免
如果一個新的串連剛建立成功就向網路中發送大量的資料包,很可能會導致網路中路由器緩衝不夠用(路由器緩衝不夠用時就會將超出其緩衝的資料包丟包,而TCP又要重傳該包,可想而知很快會導致網路出現嚴重擁塞,實際上網路中傳輸的大部分是路由器由於緩衝不夠而丟掉導致重複傳輸的包),所以一開始建立的新串連不能發送大量報文,而是先從一個較低的起點開始發送,如果發送得到及時確認,那麼就將這個起點增大一點。
發送方維護著一個擁塞視窗(cwnd),擁塞視窗的大小隨著網路負載的變化而即時變化,發送方每次發送資料的大小不得大於擁塞視窗和對端接受視窗的最小值。
這裡圖方便以報文段大小為單位說明TCP的擁塞控制,實際上是以位元組為單位的。
發送發每發送成功一位元組就把視窗cwnd加1,這樣其實我們注意到發送視窗是以2的指數增長的,所謂乘性增。因此,說是慢開始指得是剛開始發送的初始視窗比較小,但是如果網路通暢的話,發送視窗的增長速度實際上是相當快的。
如果一直以這樣的速度增長下去最終也會造成網路擁塞,所以TCP除了cwnd意外還設定了一個慢開始門限ssthresh變數。
當cwnd < ssthresh的時候使用慢開始演算法,當cwnd>ssthresh時使用擁塞避免演算法。擁塞避免演算法是每經過一個RTT的時候就將cwnd加1(注意到這裡,慢開始演算法是每發送成功一位元組就把cwnd加1,經過RTT發送成功了多少位元組就增加了多少位元組,也就是發送的位元組數*2,而擁塞避免演算法則不管發送成功多少位元組都僅將cwnd加1),此時cwnd的變化開始平緩。無論是在慢開始階段還是在擁塞避免階段,只要網路發生擁塞(判斷的依據是丟包),都將慢開始門限ssthresh減小為原來的一般並將發送視窗cwnd置為1,執行慢開始演算法。
(2)快重傳和快恢複
我們知道TCP在發送一個資料包後會設定一個定時器,如果這個定時器到時後仍然沒有接受到對端的確認,那麼就確認該報文已丟失需要重新發送,但是如果每個已丟失的報文都需要等到定時器到達在重新發送那麼傳輸效率勢必會造成影響。因此TCP採用快重傳來使得TCP擁有更高效的傳輸速率。快重傳的意思是指接受方沒接受到一個失序的報文就立即確認該報文,而不是採用上文所講的延時確認。當接收方連續接收到三個失序的確認報文後就馬上重新發送導致失序的那個報文(這個報文未被確認而導致收到了三個失序的確認報文),而不必等待重傳定時器到時。
配合快重傳演算法的還有快恢複演算法,當發送方連續收到三個重複的確認報文時就採用快恢複演算法。此時先把sthresh減半,cwnd設定為sthresh相同大小,同時執行擁塞避免演算法。注意到此次並未將cwnd置為1,因為TCP認為能夠連續收到三個確認報文,所以網路可能並不擁塞,因此只採用了快恢複演算法。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
TCP的流量控制和擁塞控制