傳輸控制協定(英語: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。
運作方式
![Thumb](http://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Tcp_state_diagram_fixed_new.svg/640px-Tcp_state_diagram_fixed_new.svg.png)
TCP協定的執行可劃分為三個階段:連接建立(connection establishment)、數據傳送(data transfer)和連接終止(connection termination)。作業系統將TCP連接抽象為通訊端表示的本地端點(local end-point),作為編程介面給程式使用。在TCP連接的生命期內,本地端點要經歷一系列的狀態改變。[1]
TCP用三路握手(或稱三次握手,three-way handshake)過程建立一個連接。在連接建立過程中,很多參數要被初始化,例如序號被初始化以保證按序傳輸和連接的強壯性。
![Thumb](http://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Connection_TCP.png/320px-Connection_TCP.png)
一對終端同時初始化一個它們之間的連接是可能的。但通常是由一端(伺服器端)打開一個通訊端(socket)然後監聽來自另一方(客戶端)的連接,這就是通常所指的被動打開(passive open)。伺服器端被被動打開以後,客戶端就能開始建立主動打開(active open)。
伺服器端執行了listen函數後,就在伺服器上建立起兩個佇列:
- SYN佇列:存放完成了二次握手的結果。 佇列長度由listen函數的參數backlog指定。
- ACCEPT佇列:存放完成了三次握手的結果。佇列長度由listen函數的參數backlog指定。
三次握手協定的過程:
- 客戶端(通過執行connect函數)向伺服器端傳送一個SYN包,請求一個主動打開。該包攜帶客戶端為這個連接請求而設定的亂數A作為訊息序列號。
- 伺服器端收到一個合法的SYN包後,把該包放入SYN佇列中;回送一個SYN/ACK。ACK的確認碼應為A+1,SYN/ACK包本身攜帶一個隨機產生的序號B。
- 客戶端收到SYN/ACK包後,傳送一個ACK包,該包的序號被設定為A+1,而ACK的確認碼則為B+1。然後客戶端的connect函數成功返回。當伺服器端收到這個ACK包的時候,把請求幀從SYN佇列中移出,放至ACCEPT佇列中;這時accept函數如果處於阻塞狀態,可以被喚醒,從ACCEPT佇列中取出ACK包,重新建立一個新的用於雙向通訊的sockfd,並返回。
如果伺服器端接到了客戶端發的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] 進一步,如果重傳定時器被觸發,仍然沒有收到確認包,定時器的值將被設為前次值的二倍(直到特定閾值)。這是由於存在一類通過欺騙傳送者使其重傳多次,進而壓垮接收者的攻擊,而使用前述的定時器策略可以避免此類中間人攻擊方式的阻斷服務攻擊。
![Thumb](http://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Tcp_transport_example.gif/640px-Tcp_transport_example.gif)
- 傳送方首先傳送第一個包含序列號為1(可變化)和1460位元組數據的TCP報文段給接收方。接收方以一個沒有數據的TCP報文段來回覆(只含報頭),用確認號1461來表示已完全收到並請求下一個報文段。
- 傳送方然後傳送第二個包含序列號為1461,長度為1460位元組的數據的TCP報文段給接收方。正常情況下,接收方以一個沒有數據的TCP報文段來回覆,用確認號2921(1461+1460)來表示已完全收到並請求下一個報文段。傳送接收這樣繼續下去。
- 然而當這些封包都是相連的情況下,接收方沒有必要每一次都回應。比如,他收到第1到5條TCP報文段,只需回應第五條就行了。在例子中第3條TCP報文段被遺失了,所以儘管他收到了第4和5條,然而他只能回應第2條。
- 傳送方在傳送了第三條以後,沒能收到回應,因此當時鐘(timer)過時(expire)時,他重發第三條。(每次傳送者傳送一條TCP報文段後,都會再次啟動一次時鐘:RTT)。
- 這次第三條被成功接收,接收方可以直接確認第5條,因為4,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使用滑動窗口協定實現流量控制。接收方在「接收窗口」域指出還可接收的位元組數量。傳送方在沒有新的確認包的情況下至多傳送「接收窗口」允許的位元組數量。接收方可修改「接收窗口」的值。
![Thumb](http://upload.wikimedia.org/wikipedia/commons/thumb/d/db/Tcp.svg/320px-Tcp.svg.png)
當接收方宣佈接收窗口的值為0,傳送方停止進一步傳送數據,開始了「保持定時器」(persist timer),以避免因隨後的修改接收窗口的封包遺失使連接的雙側進入死結,傳送方無法發出數據直至收到接收方修改窗口的指示。當「保持定時器」到期時,TCP傳送方嘗試恢復傳送一個小的ZWP包(Zero Window Probe),期待接收方回覆一個帶着新的接收窗口大小的確認包。一般ZWP包會設置成3次,如果3次過後還是0的話,有的TCP實現就會發RST把連結斷了。
如果接收方以很小的增量來處理到來的數據,它會發佈一系列小的接收窗口。這被稱作愚蠢窗口綜合症,因為它在TCP的封包中傳送很少的一些位元組,相對於TCP包頭是很大的開銷。解決這個問題,就要避免對小的window size做出響應,直到有足夠大的window size再響應:
- 接收端使用David D Clark演算法:如果收到的數據導致window size小於某個值,可以直接ack把window給關閉了,阻止了傳送端再發數據。等到接收端處理了一些數據後windows size大於等於了MSS,或者接收端buffer有一半為空,就可以把window打開讓傳送端再發數據過來。
- 傳送端使用Nagle演算法來延時處理,條件一:Window Size>=MSS 且 Data Size >=MSS;條件二:等待時間或是逾時200ms,這兩個條件有一個滿足,才會發數據,否則就是在積累數據。Nagle演算法預設是打開的,所以對於一些需要小包場景的程式——比如像telnet或ssh這樣的互動性程式,需要關閉這個演算法。可以在Socket設置TCP_NODELAY選項來關閉這個演算法。
擁塞控制是傳送方根據網絡的承載情況控制分組的傳送量,以取得高效能又能避免擁塞崩潰(congestion collapse,網絡效能下降幾個數量級)。這在網絡流之間產生近似最大最小公平分配。
傳送方與接收方根據確認包或者包遺失的情況,以及定時器,估計網絡擁塞情況,從而修改數據流的行為,這稱為擁塞控制或網絡擁塞避免。
TCP的現代實現包含四種相互影響的擁塞控制演算法:慢開始、擁塞避免、快速重傳、快速恢復。
此外,傳送方採取「逾時重傳」(retransmission timeout,RTO),這是估計出來回通訊延遲 (RTT) 以及RTT的方差。
RFC793中定義的計算SRTT的經典演算法:指數加權移動平均(Exponential weighted moving average)
- 先採樣RTT,記下最近好幾次的RTT值。
- 做平滑計算SRTT公式為:,其中 α 取值在0.8 到 0.9之間
- 計算RTO,公式:,其中 UBOUND是最大的timeout時間上限值,LBOUND是最小的timeout時間下限值,β值一般在1.3到2.0之間。
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的第一個段來做標誌,
- 如果SACK的第一個段的範圍被ACK所覆蓋,那麼就是D-SACK;
- 如果SACK的第一個段的範圍被SACK的第二個段覆蓋,那麼就是D-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應當成比例。
有兩個時間戳域:
- 4位元組的傳送時間戳值
- 4位元組的響應回覆時間戳值(最近收到數據的時間戳)
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]
![Thumb](http://upload.wikimedia.org/wikipedia/commons/thumb/2/2d/Deconnection_TCP.png/320px-Deconnection_TCP.png)
![Thumb](http://upload.wikimedia.org/wikipedia/commons/thumb/5/55/TCP_CLOSE.svg/320px-TCP_CLOSE.svg.png)
連接終止使用了四路握手過程(或稱四次握手,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]
- LISTEN S
- 伺服器等待從任意遠端TCP埠的連接請求。偵聽狀態。
- SYN-SENT C
- 客戶在傳送連接請求後等待匹配的連接請求。通過connect()函數向伺服器發出一個同步(SYNC)訊號後進入此狀態。
- SYN-RECEIVED S
- 伺服器已經收到並行送同步(SYNC)訊號之後等待確認(ACK)請求。
- ESTABLISHED S&C
- 伺服器與客戶的連接已經打開,收到的數據可以傳送給用戶。數據傳輸步驟的正常情況。此時連接兩端是平等的。這稱作全連接。
- FIN-WAIT-1 S&C
- (伺服器或客戶)主動關閉端呼叫close()函數發出FIN請求包,表示本方的數據傳送全部結束,等待TCP連接另一端的ACK確認包或FIN&ACK請求包。
- FIN-WAIT-2 S&C
- 主動關閉端在FIN-WAIT-1狀態下收到ACK確認包,進入等待遠端TCP的連接終止請求的半關閉狀態。這時可以接收數據,但不再傳送數據。
- CLOSE-WAIT S&C
- 被動關閉端接到FIN後,就發出ACK以回應FIN請求,並進入等待本地用戶的連接終止請求的半關閉狀態。這時可以傳送數據,但不再接收數據。
- CLOSING S&C
- 在發出FIN後,又收到對方發來的FIN後,進入等待對方對己方的連接終止(FIN)的確認(ACK)的狀態。少見。
- LAST-ACK S&C
- 被動關閉端全部數據傳送完成之後,向主動關閉端傳送FIN,進入等待確認包的狀態。
- TIME-WAIT S/C
- 主動關閉端接收到FIN後,就傳送ACK包,等待足夠時間以確保被動關閉端收到了終止請求的確認包。(按照RFC 793,一個連接可以在TIME-WAIT保證最大四分鐘,即最大分段壽命(maximum segment lifetime)的2倍)
- CLOSED S&C
- 完全沒有連接。
埠
TCP使用了通訊埠(Port number)的概念來標識傳送方和接收方的應用層。對每個TCP連接的一端都有一個相關的16位元的無符號埠號分配給它們。埠被分為三類:眾所周知的、註冊的和動態/私有的。眾所周知的埠號是由互聯網賦號管理局(IANA)來分配的,並且通常被用於系統一級或根行程。眾所周知的應用程式作為伺服器程式來執行,並被動地偵聽經常使用這些埠的連接。例如:FTP、TELNET、SMTP、HTTP、IMAP、POP3等。註冊的埠號通常被用來作為終端用戶連接伺服器時短暫地使用的源埠號,但它們也可以用來標識已被第三方註冊了的、被命名的服務。動態/私有的埠號在任何特定的TCP連接外不具有任何意義。可能的、被正式承認的埠號有65535個。
封包結構
- 來源連接埠(16位元長)-辨識傳送連接埠
- 目的連接埠(16位元長)-辨識接收連接埠
- 序列號(seq,32位元長)
- 如果含有同步化旗標(SYN),則此為最初的序列號;第一個資料位元的序列碼為本序列號加一。
- 如果沒有同步化旗標(SYN),則此為第一個資料位元的序列碼。
- 確認號(ack,32位元長)—期望收到的數據的開始序列號。也即已經收到的數據的位元組長度加1。
- 資料偏移(4位元長)—以4位元組為單位計算出的數據段開始地址的偏移值。
- 保留(3位元長)—須置0
- 標誌符(9位元長)
- NS—ECN-nonce。ECN顯式擁塞通知(Explicit Congestion Notification)是對TCP的擴充,定義於 RFC 3540 (2003)。ECN允許擁塞控制的端對端通知而避免丟包。ECN為一項可選功能,如果底層網絡設施支援,則可能被啟用ECN的兩個端點使用。在ECN成功協商的情況下,ECN感知路由器可以在IP頭中設置一個標記來代替丟棄封包,以標明阻塞即將發生。封包的接收端回應傳送端的表示,降低其傳輸速率,就如同在往常中檢測到包遺失那樣。
- CWR—Congestion Window Reduced,定義於 RFC 3168(2001)。
- ECE—ECN-Echo有兩種意思,取決於SYN標誌的值,定義於 RFC 3168(2001)。
- URG—為1表示高優先級封包,緊急指標欄位有效。
- ACK—為1表示確認號欄位有效
- PSH—為1表示是帶有PUSH標誌的數據,指示接收方應該儘快將這個報文段交給應用層而不用等待緩衝區裝滿。
- RST—為1表示出現嚴重差錯。可能需要重新建立TCP連接。還可以用於拒絕非法的報文段和拒絕連接請求。
- SYN—為1表示這是連接請求或是連接接受請求,用於建立連接和使順序號同步
- FIN—為1表示傳送方沒有數據要傳輸了,要求釋放連接。
- 窗口(WIN,16位元長)—表示從確認號開始,本報文的傳送方可以接收的位元組數,即接收窗口大小。用於流量控制。
- 校驗和(Checksum,16位元長)—對整個的TCP報文段,包括TCP頭部和TCP數據,以16位元字進行計算所得。這是一個強制性的欄位。
- 緊急指標(16位元長)—本報文段中的緊急數據的最後一個位元組的序號。
- 選項欄位—最多40位元組。每個選項的開始是1位元組的kind欄位,說明選項的類型。
- 0:選項表結束(1位元組)
- 1:無操作(1位元組)用於選項欄位之間的字邊界對齊。
- 2:最大報文段長度(4位元組,Maximum Segment Size,MSS)通常在建立連接而設置SYN標誌的封包中指明這個選項,指明本端所能接收的最大長度的報文段。通常將MSS設置為(MTU-40)位元組,攜帶TCP報文段的IP數據報的長度就不會超過MTU(MTU最大長度為1518位元組,最短為64位元組),從而避免本機發生IP分片。只能出現在同步報文段中,否則將被忽略。
- 3:窗口擴大因子(3位元組,wscale),取值0-14。用來把TCP的窗口的值左移的位數,使窗口值乘倍。只能出現在同步報文段中,否則將被忽略。這是因為現在的TCP接收數據緩衝區(接收窗口)的長度通常大於65535位元組。
- 4:sackOK—傳送端支援並同意使用SACK選項。
- 5:SACK實際工作的選項。
- 8:時間戳(10位元組,TCP Timestamps Option,TSopt)
- 傳送端的時間戳(Timestamp Value field,TSval,4位元組)
- 時間戳回顯應答(Timestamp Echo Reply field,TSecr,4位元組)
- 19:MD5摘要,將TCP偽首部、校驗和為0的TCP首部、TCP數據段、通訊雙方約定的金鑰(可選)計算出MD5摘要值並附加到該選項中,作為類似對TCP報文的簽章。通過 RFC 2385 引入,主要用於增強BGP通訊的安全性。
- 29:安全摘要,通過 RFC 5925 引入,將「MD5摘要」的雜湊方法更換為SHA雜湊演算法。
發展過程
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。