Loading AI tools
用於在網際協議網絡上傳輸數據的主要協議 来自维基百科,自由的百科全书
傳輸控制協定(英語:Transmission Control Protocol,縮寫:TCP)是一種連接導向的、可靠的、基於位元組流的傳輸層通訊協定,由IETF的RFC 793定義。在簡化的電腦網路OSI模型中,它完成第四層傳輸層所指定的功能。使用者資料報協定(UDP)是同一層內另一個重要的傳輸協定。
此條目或許過多或不當使用受版權保護的文字、圖片及多媒體檔案。 (2017年11月25日) |
![]() | 此條目包含過多行話或專業術語,可能需要簡化或提出進一步解釋。 (2018年1月31日) |
在網際網路協定族(Internet protocol suite)中,TCP層是位於IP層之上,應用層之下的中間層。不同主機的應用層之間經常需要可靠的、像管道一樣的連接,但是IP層不提供這樣的流機制,而是提供不可靠的包交換。
應用層向TCP層傳送用於網間傳輸的、用8位元位元組表示的資料流,然後TCP把資料流分割成適當長度的報文段(通常受該電腦連接的網路的資料鏈路層的最大傳輸單元(MTU)的限制)。之後TCP把結果包傳給IP層,由它來透過網路將包傳送給接收端實體的TCP層。TCP為了保證不發生丟包,就給每個包一個序號,同時序號也保證了傳送到接收端實體的包的按序接收。然後接收端實體對已成功收到的包發回一個相應的確認資訊(ACK);如果傳送端實體在合理的往返時延(RTT)內未收到確認,那麼對應的封包就被假設為已遺失並進行重傳。TCP用一個校驗和函式來檢驗資料是否有錯誤,在傳送和接收時都要計算校驗和。
資料在TCP層稱為流(Stream),資料分組稱為分段(Segment)。作為比較,資料在IP層稱為Datagram,資料分組稱為分片(Fragment)。 UDP 中分組稱為Message。
TCP協定的執行可劃分為三個階段:連接建立(connection establishment)、資料傳送(data transfer)和連接終止(connection termination)。作業系統將TCP連接抽象為通訊端表示的本地端點(local end-point),作為編程介面給程式使用。在TCP連接的生命期內,本地端點要經歷一系列的狀態改變。[1]
TCP用三路握手(或稱三次握手,three-way handshake)過程建立一個連接。在連接建立過程中,很多參數要被初始化,例如序號被初始化以保證按序傳輸和連接的強壯性。
一對終端同時初始化一個它們之間的連接是可能的。但通常是由一端(伺服器端)打開一個通訊端(socket)然後監聽來自另一方(客戶端)的連接,這就是通常所指的被動打開(passive open)。伺服器端被被動打開以後,客戶端就能開始建立主動打開(active open)。
伺服器端執行了listen函式後,就在伺服器上建立起兩個佇列:
三次握手協定的過程:
如果伺服器端接到了客戶端發的SYN後回了SYN-ACK後客戶端斷線了,伺服器端沒有收到客戶端回來的ACK,那麼,這個連接處於一個中間狀態,既沒成功,也沒失敗。於是,伺服器端如果在一定時間內沒有收到的TCP會重發SYN-ACK。在Linux下,預設重試次數為5次,重試的間隔時間從1s開始每次都翻倍,5次的重試時間間隔為1s, 2s, 4s, 8s, 16s,總共31s,第5次發出後還要等32s才知道第5次也逾時了,所以,總共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 63s,TCP才會斷開這個連接。使用三個TCP參數來調整行為:tcp_synack_retries 減少重試次數;tcp_max_syn_backlog,增大SYN連接數;tcp_abort_on_overflow決定超出能力時的行為。
「三次握手」的目的是「為了防止已失效的連接(connect)請求報文段傳送到了伺服器端,因而產生錯誤」,也即為了解決「網路中存在延遲的重複分組」問題。例如:client發出的第一個連接請求報文段並沒有遺失,而是在某個網路結點長時間的滯留了,以致延誤到連接釋放以後的某個時間才到達server。本來這是一個早已失效的報文段。但server收到此失效的連接請求報文段後,就誤認為是client發出的一個新的連接請求。於是就向client發出確認報文段,同意建立連接。假設不採用「三次握手」,那麼只要server發出確認,新的連接就建立了。由於現在client並沒有發出建立連接的請求,因此不會理睬server的確認,也不會向server傳送資料。但server卻以為新的運輸連接已經建立,並一直等待client發來資料。這樣,server的很多資源就白白浪費掉了。採用「三次握手」的辦法可以防止上述現象發生,client不會向server的確認發出確認。server由於收不到確認,就知道client並沒有要求建立連接。
主機收到一個TCP包時,用兩端的IP位址與埠號來標識這個TCP包屬於哪個session。使用一張表來儲存所有的session,表中的每條稱作Transmission Control Block(TCB),tcb結構的定義包括連接使用的源埠、目的埠、目的ip、序號、應答序號、對方窗口大小、己方窗口大小、tcp狀態、tcp輸入/輸出佇列、應用層輸出佇列、tcp的重傳有關變數等。
伺服器端的連接數量是無限的,只受主記憶體的限制。客戶端的連接數量,過去由於在傳送第一個SYN到伺服器之前需要先分配一個隨機空閒的埠,這限制了客戶端IP位址的對外發出連接的數量上限。從Linux 4.2開始,有了socket選項IP_BIND_ADDRESS_NO_PORT,它通知Linux核心不保留usingbind使用埠號為0時內部使用的臨時埠(ephemeral port),在connect時會自動選擇埠以組成獨一無二的四元組(同一個客戶端埠可用於連接不同的伺服器通訊端;同一個伺服器埠可用於接受不同客戶端通訊端的連接)。[2]
對於不能確認的包、接收但還沒讀取的資料,都會占用作業系統的資源。
在TCP的資料傳送狀態,很多重要的機制保證了TCP的可靠性和強壯性。它們包括:使用序號,對收到的TCP報文段進行排序以及檢測重複的資料;使用校驗和檢測報文段的錯誤,即無錯傳輸[3];使用確認和計時器來檢測和糾正丟包或延時;流控制(Flow control);擁塞控制(Congestion control);遺失包的重傳。
通常在每個TCP報文段中都有一對序號和確認號。TCP報文傳送者稱自己的位元組流的編號為序號(sequence number),稱接收到對方的位元組流編號為確認號。TCP報文的接收者為了確保可靠性,在接收到一定數量的連續位元組流後才傳送確認。這是對TCP的一種擴充,稱為選擇確認(Selective Acknowledgement)。選擇確認使得TCP接收者可以對亂序到達的資料塊進行確認。每一個位元組傳輸過後,SN號都會遞增1。
通過使用序號和確認號,TCP層可以把收到的報文段中的位元組按正確的順序交付給應用層。序號是32位元的無符號數,在它增大到232-1時,便會迴繞到0。對於初始化序列號(ISN)的選擇是TCP中關鍵的一個操作,它可以確保強壯性和安全性。
TCP協定使用序號標識每端發出的位元組的順序,從而另一端接收資料時可以重建順序,無懼傳輸時的包的亂序交付或丟包。在傳送第一個包時(SYN包),選擇一個亂數作為序號的初值,以克制TCP序號預測攻擊.
傳送確認包(Acks),攜帶了接收到的對方發來的位元組流的編號,稱為確認號,以告訴對方已經成功接收的資料流的位元組位置。Ack並不意味著資料已經交付了上層應用程式。
可靠性通過傳送方檢測到遺失的傳輸資料並重傳這些資料。包括逾時重傳(Retransmission timeout,RTO)與重複累計確認(duplicate cumulative acknowledgements,DupAcks)。
如果一個包(不妨設它的序號是100,即該包始於第100位元組)遺失,接收方就不能確認這個包及其以後的包,因為採用了累計ack。接收方在收到100以後的包時,發出對包含第99位元組的包的確認。這種重複確認是包遺失的訊號。傳送方如果收到3次對同一個包的確認,就重傳最後一個未被確認的包。閾值設為3被證實可以減少亂序包導致的無作用的重傳(spurious retransmission)現象。[4] 選擇性確認(SACK)的使用能明確回饋哪個包收到了,極大改善了TCP重傳必要的包的能力。
傳送方使用一個保守估計的時間作為收到封包的確認的逾時上限。如果超過這個上限仍未收到確認包,傳送方將重傳這個封包。每當傳送方收到確認包後,會重設這個重傳定時器。典型地,定時器的值設定為 其中是時鐘粒度。[5] 進一步,如果重傳定時器被觸發,仍然沒有收到確認包,定時器的值將被設為前次值的二倍(直到特定閾值)。這是由於存在一類通過欺騙傳送者使其重傳多次,進而壓垮接收者的攻擊,而使用前述的定時器策略可以避免此類中間人攻擊方式的阻斷服務攻擊。
TCP的16位元的校驗和(checksum)的計算和檢驗過程如下:傳送者將TCP報文段的頭部和資料部分的和計算出來,再對其求一補數(一的補數),就得到了校驗和,然後將結果裝入報文中傳輸。(這裡用一補數和的原因是這種方法的迴圈進位使校驗和可以在16位元、32位元、64位元等情況下的計算結果再疊加後相同)接收者在收到報文後再按相同的演算法計算一次校驗和。這裡使用的一補數使得接收者不用再將校驗和欄位儲存起來後清零,而可以直接將報文段連同校驗加總。如果計算結果是全部為一,那麼就表示了報文的完整性和正確性。
注意:TCP校驗和也包括了96位的偽頭部,其中有源位址、目的位址、協定以及TCP的長度。這可以避免報文被錯誤地路由。
按現在的標準,TCP的校驗和是一個比較脆弱的校驗。出錯概率高的資料鏈路層需要更高的能力來探測和糾正連接錯誤。TCP如果是在今天設計的,它很可能有一個32位元的CRC校驗來糾錯,而不是使用校驗和。但是通過在第二層使用通常的CRC校驗或更完全一點的校驗可以部分地彌補這種脆弱的校驗。第二層是在TCP層和IP層之下的,比如PPP或乙太網路,它們使用了這些校驗。但是這也並不意味著TCP的16位元校驗和是冗餘的,對於網際網路傳輸的觀察,表明在受CRC校驗保護的各跳之間,軟體和硬體的錯誤通常也會在報文中引入錯誤,而端到端的TCP校驗能夠捕捉到大部分簡單的錯誤。[6] 這就是應用中的端到端原則。
流量控制用來避免主機分組傳送得過快而使接收方來不及完全收下,一般由接收方通告給傳送方進行調控。
TCP使用滑動窗口協定實現流量控制。接收方在「接收窗口」域指出還可接收的位元組數量。傳送方在沒有新的確認包的情況下至多傳送「接收窗口」允許的位元組數量。接收方可修改「接收窗口」的值。
當接收方宣布接收窗口的值為0,傳送方停止進一步傳送資料,開始了「保持定時器」(persist timer),以避免因隨後的修改接收窗口的封包遺失使連接的雙側進入死結,傳送方無法發出資料直至收到接收方修改窗口的指示。當「保持定時器」到期時,TCP傳送方嘗試恢復傳送一個小的ZWP包(Zero Window Probe),期待接收方回覆一個帶著新的接收窗口大小的確認包。一般ZWP包會設定成3次,如果3次過後還是0的話,有的TCP實現就會發RST把連結斷了。
如果接收方以很小的增量來處理到來的資料,它會發布一系列小的接收窗口。這被稱作愚蠢窗口綜合症,因為它在TCP的封包中傳送很少的一些位元組,相對於TCP包頭是很大的開銷。解決這個問題,就要避免對小的window size做出回應,直到有足夠大的window size再回應:
擁塞控制是傳送方根據網路的承載情況控制分組的傳送量,以取得高效能又能避免擁塞崩潰(congestion collapse,網路效能下降幾個數量級)。這在網路流之間產生近似最大最小公平分配。
傳送方與接收方根據確認包或者包遺失的情況,以及定時器,估計網路擁塞情況,從而修改資料流的行為,這稱為擁塞控制或網路擁塞避免。
TCP的現代實現包含四種相互影響的擁塞控制演算法:慢開始、擁塞避免、快速重傳、快速恢復。
此外,傳送方採取「逾時重傳」(retransmission timeout,RTO),這是估計出來回通訊延遲 (RTT) 以及RTT的方差。
RFC793中定義的計算SRTT的經典演算法:指數加權移動平均(Exponential weighted moving average)
1987年,出現計算RTT的Karn演算法或TCP時間戳(RFC 1323),最大特點是——忽略重傳,不把重傳的RTT做採樣。但是,如果在某一時間,網路閃動,突然變慢了,產生了比較大的延時,這個延時導致要重傳所有的包(因為之前的RTO很小),於是,因為重傳的不算,所以,RTO就不會被更新,這是一個災難。為此,Karn演算法一發生重傳,就對現有的RTO值翻倍。這就是的Exponential backoff。
1988年,在RFC 6298中給出范·雅各布森演算法取平均以獲得平滑往返時延(Smoothed Round Trip Time,SRTT),作為最終的RTT估計值。這個演算法在被用在今天的TCP協定中:
其中:DevRTT是Deviation RTT。在Linux下,α = 0.125,β = 0.25, μ = 1,∂= 4
目前有很多TCP擁塞控制演算法在研究中。
最大分段大小 (MSS)是在單個分段中TCP願意接受的資料的位元組數最大值。MSS應當足夠小以避免IP分片,它會導致丟包或過多的重傳。在TCP連接建立時,雙端在SYN報文中用MSS選項宣布各自的MSS,這是從雙端各自直接相連的資料鏈路層的最大傳輸單元(MTU)的尺寸減去固定的IP首部和TCP首部長度。乙太網路MTU為1500位元組, MSS值可達1460位元組。使用IEEE 802.3的MTU為1492位元組,MSS可達1452位元組。如果目的IP位址為「非本地的」,MSS通常的預設值為536(這個預設值允許20位元組的IP首部和20位元組的TCP首部以適合576位元組IP資料報)。此外,傳送方可用傳輸路徑MTU發現(RFC 1191)推導出從傳送方到接收方的網路路徑上的最小MTU,以此動態調整MSS以避免網路IP分片。
MSS發布也被稱作「MSS協商」(MSS negotiation)。嚴格講,這並非是協商出來一個統一的MSS值,TCP允許連接兩端使用各自不同的MSS值。[7] 例如,這會發生在參與TCP連接的一台裝置使用非常少的主記憶體處理到來的TCP分組。
最初採取累計確認的TCP協定在丟包時效率很低。例如,假設通過10個分組發出了1萬個位元組的資料。如果第一個分組遺失,在純粹的累計確認協定下,接收方不能說它成功收到了1,000到9,999位元組,但未收到包含0到999位元組的第一個分組。因而,傳送方可能必須重傳所有1萬個位元組。
為此,TCP採取了「選擇確認」(selective acknowledgment,SACK)選項。RFC 2018 對此定義為允許接收方確認它成功收到的分組的不連續的塊,以及基礎TCP確認的成功收到最後連續位元組序號。這種確認可以指出SACK block,包含了已經成功收到的連續範圍的開始與結束位元組序號。在上述例子中,接收方可以發出SACK指出序號1000到9999,傳送方因此知道只需重發第一個分組(位元組 0 到 999)。
TCP傳送方會把亂序收包當作丟包,因此會重傳亂序收到的包,導致連接的效能下降。重複SACK選項(duplicate-SACK option)是定義在RFC 2883中的SACK的一項擴充,可解決這一問題。接收方發出D-ACK指出沒有丟包,接收方恢復到高傳輸率。D-SACK使用了SACK的第一個段來做標誌,
D-SACK旨在告訴傳送端:收到了重複的資料,封包沒有丟,丟的是ACK包;或者「Fast Retransmit演算法」觸發的重傳不是因為發出去的包丟了,也不是因為回應的ACK包丟了,而是因為網路延時導致的reordering。
SACK選項並不是強制的。僅當雙端都支援時才會被使用。TCP連接建立時會在TCP頭中協商SACK細節。在 Linux下,可以通過tcp_sack參數打開SACK功能(Linux 2.4後預設打開)。Linux下的tcp_dsack參數用於開啟D-SACK功能(Linux 2.4後預設打開)。選擇確認也用於流控制傳輸協定 (SCTP).
TCP窗口尺寸域控制封包在2至65,535位元組。RFC 1323 定義的TCP窗口縮放選項用於把最大窗口尺寸從65,535位元組擴大至1G位元組。擴大窗口尺寸是TCP最佳化的需要。
窗口縮放選項盡在TCP三次握手時雙端在SYN包中獨立指出這個方向的縮放係數。該值是16位元窗口尺寸的向左位移數,從0 (表示不位移)至14。
某些路由器或分組防火牆會重寫窗口縮放選項,這可能導致不穩定的網路傳輸。[8]
RFC 1323 定義了TCP時間戳,並不對應於系統時鐘,使用隨機值初始化。許多作業系統每毫秒增加一次時間戳;但RFC只規定tick應當成比例。
有兩個時間戳域:
TCP時間戳用於「防止序列號迴繞演算法」(Protection Against Wrapped Sequence numbers,PAWS),細節見RFC 1323。PAWS用於接收窗口跨序號迴繞邊界。這種情形下一個包可能會重傳以回答問題:「是否是第一個還是第二個4 GB的序號?」時間戳可以打破這一問題。
另外,Eifel檢測演算法( RFC 3522 )使用TCP時間戳確定如果重傳發生是因為丟包還是簡單亂序。
最近統計表明時間戳的採用率停滯在~40%,這歸因於Windows伺服器從Windows Server 2008起降低了支援。[9].
帶外資料(OOB)是指對緊急資料,中斷或放棄排隊中的資料流;接收方應立即處理緊急資料。完成後,TCP通知應用程式恢復流佇列的正常處理。
正常情況下,TCP等待200 ms以準備一個完整分組發出(納格演算法試圖把小的資訊組裝為單一的包)。這產生了小的、但潛在很嚴重的延遲並在傳遞一個檔案時不斷重複延遲。例如,典型傳送塊是4 KB,典型的MSS是1460位元組,在10 Mbit/s乙太網路上發出兩個包,每個耗時約~1.2 ms,隨後是剩餘1176個位元組的包,之後是197 ms停頓因為TCP等待裝滿緩衝區。
對於telnet,每次使用者擊鍵的回應,如果有200 ms將會非常煩人。
socket選項TCP_NODELAY
能放棄預設的200 ms傳送延遲。應用程式使用這個socket選項強制發出資料。
RFC定義了PSH
能立即發出位元。Berkeley通訊端不能控制或指出這種情形,只能由協定棧控制。[12]
連接終止使用了四路握手過程(或稱四次握手,four-way handshake),在這個過程中連接的每一側都獨立地被終止。當一個端點要停止它這一側的連接,就向對側傳送FIN,對側回覆ACK表示確認。因此,拆掉一側的連接過程需要一對FIN和ACK,分別由兩側端點發出。
首先發出FIN的一側,如果給對側的FIN回應了ACK,那麼就會逾時等待2*MSL時間,然後關閉連接。在這段逾時等待時間內,本地的埠不能被新連接使用;避免延時的包的到達與隨後的新連接相混淆。RFC793定義了MSL為2分鐘,Linux設定成了30s。參數tcp_max_tw_buckets控制並行的TIME_WAIT的數量,預設值是180000,如果超限,那麼,系統會把多的TIME_WAIT狀態的連接給destory掉,然後在紀錄檔里打一個警告(如:time wait bucket table overflow)
連接可以工作在TCP半開狀態。即一側關閉了連接,不再傳送資料;但另一側沒有關閉連接,仍可以傳送資料。已關閉的一側仍然應接收資料,直至對側也關閉了連接。
也可以通過測三路握手關閉連接。主機A發出FIN,主機B回覆FIN & ACK,然後主機A回覆ACK.[13]
一些主機(如Linux或HP-UX)的TCP棧能實現半雙工關閉序列。這種主機如果主動關閉一個連接但還沒有讀完從這個連接已經收到的資料,該主機傳送RST代替FIN[14]。這使得一個TCP應用程式能確認遠端應用程式已經讀了所有已傳送資料,並等待遠端側發出的FIN。但是遠端的TCP棧不能區分Connection Aborting RST與Data Loss RST,兩種原因都會導致遠端的TCP棧失去所有的收到資料。
一些應用協定使用TCP open/close handshaking,因為應用協定的TCP open/close handshaking可以發現主動關閉的RST問題。例如:
s = connect(remote); send(s, data); close(s);
TCP/IP棧採用上述方法不能保證所有資料到達對側,如果未讀資料已經到達對側。
下表為TCP狀態碼列表,以S指代伺服器,C指代客戶端,S&C表示兩者,S/C表示兩者之一:[1]
TCP使用了通訊埠(Port number)的概念來標識傳送方和接收方的應用層。對每個TCP連接的一端都有一個相關的16位元的無符號埠號分配給它們。埠被分為三類:眾所周知的、註冊的和動態/私有的。眾所周知的埠號是由網際網路賦號管理局(IANA)來分配的,並且通常被用於系統一級或根行程。眾所周知的應用程式作為伺服器程式來執行,並被動地偵聽經常使用這些埠的連接。例如:FTP、TELNET、SMTP、HTTP、IMAP、POP3等。註冊的埠號通常被用來作為終端使用者連接伺服器時短暫地使用的源埠號,但它們也可以用來標識已被第三方註冊了的、被命名的服務。動態/私有的埠號在任何特定的TCP連接外不具有任何意義。可能的、被正式承認的埠號有65535個。
TCP是一個複雜的但同時又是在發展之中的協定。儘管許多重要的改進被提出和實施,發表於1981年的RFC793中說明的TCP(TCP-Tahoe)的許多基本操作還是未作多大改動。RFC1122:《網際網路對主機的要求》闡明了許多TCP協定的實現要求。RFC2581:《TCP的擁塞控制》是一篇近年來關於TCP的很重要的RFC,描述了更新後的避免過度擁塞的演算法。寫於2001年的RFC3168描述了對明顯擁塞的報告,這是一種擁塞避免的號誌機制。在21世紀早期,在所有網際網路的封包中,通常有大約95%的包使用了TCP協定。常見的使用TCP的應用層有HTTP/HTTPS(全球資訊網協定),SMTP/POP3/IMAP(電子郵件協定)以及FTP(檔案傳輸協定)。這些協定在今天被廣泛地使用,這證明了它們的原作者的創造是卓越的。
最近,一個新協定已經被加州理工學院的科研人員開發出來,命名為FAST TCP(基於快速活動佇列管理的規模可變的傳輸控制協定)。它使用排隊延遲作為擁塞控制訊號;但是因為端到端的延遲通常不僅僅包括排隊延遲,所以FAST TCP(或更一般地,所有基於排隊延遲的演算法)在實際網際網路中的能否工作仍然是一個沒有解決的問題。
TCP並不是對所有的應用都適合,一些新的帶有一些內在的脆弱性的運輸層協定也被設計出來。比如,即時應用並不需要甚至無法忍受TCP的可靠傳輸機制。在這種類型的應用中,通常允許一些丟包、出錯或擁塞,而不是去校正它們。例如通常不使用TCP的應用有:串流媒體、網路遊戲、IP電話(VoIP)等等。任何不是很需要可靠性或者是想將功能減到最少的應用可以避免使用TCP。在很多情況下,當只需要多路復用應用服務時,使用者資料報協定(UDP)可以代替TCP為應用提供服務。
除外,由於TCP的實現是由作業系統提供,而TCP的悠久歷史、系統級別的組態機制,一些特性在特定的網路環境下會成為一種累贅而且無法最佳化,所以也有一些通過在UDP上重新實現使用者層級的類似TCP的連接導向的、可靠的、基於位元組流的類傳輸層協定,來代替TCP,例如基於UDP的資料傳輸協定、QUIC。