Remove ads
用於在網際協議網絡上傳輸數據的主要協議 来自维基百科,自由的百科全书
傳輸控制協議(英語: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。
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.