TCP是一個面向鏈接的協議,不管哪一方向另外一方發送數據以前,都必須先在雙方之間創建一條鏈接。算法
爲了瞭解一個TCP鏈接在創建及終止時發生了什麼,鍵入下列命令:
telnet 10.31.10.19 7073
而後鍵入Ctrl+],使Telnet客戶進程終止鏈接。服務器
telnet命令與10.31.10.19:7073創建TCP鏈接。
tcpdump的輸出,如圖:網絡
(1)請求端(客戶)發送一個SYN段指明客戶打算鏈接的服務器的端口,以及初始序號ISN。
(2)服務器發回包含服務器的初始序號的SYN報文段做爲應答。同時,將確認序號設置爲客戶端的ISN加1以對客戶的SYN報文段進行確認。一個SYN將佔用一個序號。
(3)客戶必須將確認序號設置爲服務器的ISN加1以對服務器的SYN報文段進行確認。
這三個報文段完成鏈接的創建,這個過程也稱爲三次握手(three-way handshake)併發
發送第一個SYN的一端將執行主動打開(active open)。接收這個SYN併發回下一個SYN的另外一端執行被動打開(passive open)。
當一端爲創建鏈接而發送它的SYN時,它爲鏈接選擇一個初始序號。ISN隨時間而變化,所以每一個鏈接都將具備不一樣的ISN。tcp
最大報文段長度(MSS)標示TCP傳往另外一端的最大塊數據的長度。當一個鏈接創建時,鏈接的雙方都要通告各自的MSS(MSS選項只能出如今SYN報文段中)。
若是一方不接收來自另外一方的MSS值,則MSS就定爲默認值536字節(這個默認值容許20字節的IP首部和20字節的TCP首部以適合576字節IP數據報)。
當TCP發送一個SYN時,它將能MSS值設置爲外出接口上的MTU長度減去固定的IP首部和TCP首部長度。操作系統
TCP首部中的RST比特是用於「復位」的。通常來講,不管什麼時候一個報文段發送出現錯誤,TCP都會發出一個復位報文段。
(1)到不存在的端口的鏈接請求
產生復位的一種常見狀況是當鏈接請求達到時,目的端口沒有進程正在聽。
(2)異常終止一個鏈接
有可能發送給一個復位報文段而不是FIN來中途釋放一個鏈接,稱這爲異常釋放。Socket API經過「linger on close」選項(SO_LINGER)提供了這種異常關閉的能力。設計
若是一方已經關閉或異常終止鏈接而另外一方卻還不知道,咱們將這一的TCP鏈接稱爲半打開(half-open)。
任何一端的主機異常均可能致使發生這種狀況。只要不打算在半打開鏈接上傳輸數據,仍處於鏈接狀態的一方就不會檢測另外一方已經出現異常。3d
兩個應用程序同時彼此執行主動打開的狀況是可能的,儘管發生的可能性極小。每一方必須發送一個SYN,且這些SYN必須傳遞給對方。這稱爲同時打開(simultaneous open)
TCP是特地設計爲了能夠處理同時打開,對於同時打開它僅創建一條鏈接而不是兩條鏈接。
兩端幾乎在同時發送SYN並進入SYN_SENT狀態。當每一端收到SYN時,狀態變爲SYN_RCVD,同時它們都再發SYN並對收到的SYN進行確認。當雙方都收到SYN及相應的ACK時,狀態都變遷爲ESTABLISHED。
如圖:blog
一個同時打開的鏈接須要交換4個報文段,比正常的三次握手多一個。此外,要注意的是咱們沒有將任何一端稱爲客戶或服務器,由於每一端既是客戶又是服務器。接口
創建一個鏈接須要三次握手,而終止一個鏈接要通過4次握手。這由TCP的半關閉(half-close)形成的。
既然一個TCP鏈接是全雙工(即數據在兩個方向上能同時傳遞),所以每一個方向必須單獨地進行關閉。這原則就是當一方完成它的數據發送任務後就能發送一個FIN來終止這個方向鏈接。當一端收到一個FIN,它必須通知應用層另外一端已經終止了那個方向的數據傳送。發送FIN一般是應用層進行關閉的結果。
收到一個FIN只意味着在這一方向上沒有數據流動。一個TCP鏈接在收到一個FIN後仍能發送數據,而這對利用半關閉的應用來講是可能的,儘管在實際應用中只有不多的TCP應用程序這樣作。
首先進行關閉的一方(即發送第一個FIN)將執行主動關閉,而另外一方(收到這個FIN)執行被動關閉。一般一方完成主動關閉而另外一方完成被動關閉。
TCP客戶端發送給一個FIN,用來關閉從客戶端到服務器的數據傳送,當服務器收到這個FIN,它發回一個ACK,確認序號爲收到的序號加1。和SYN同樣,一個FIN將佔用一個序號。同時TCP服務器還向應用程序傳送一個文件結束符。接着這個服務器程序就關閉它的鏈接,致使它的TCP端發送一個FIN,客戶必須發回一個確認,並將確認序號設置爲收到序號加1。
如圖,三次握手和四次揮手:
TCP提供了鏈接的一端在結束它的發送後還能接收來自另外一端數據的能力。這就是所謂的半關閉。
若是應用程序不調用close而調用shutdown,則Socket API支持半關閉。然而,大多數的應用程序經過調用close終止兩個方向的鏈接。
TIME_WAIT狀態也稱爲2MSL等待狀態。每一個具體TCP實現必須選擇一個報文段最大生存時間MSL(Maximum Segment Lifetime)。
它是任何報文段被丟棄前在網絡內的最長時間。這個時間是有限的,由於TCP報文段以IP數據包在網絡內傳輸,而IP數據包則有限制其生存時間的TTL字段。
當TCP執行一個主動關閉,併發回最後一個ACK,該鏈接必須在TIME_WAIT狀態停留的時間爲2MSL。這樣可以讓TCP再次發送最後的ACK以防這個ACK丟失(另外一端超時並重發最後的FIN)。
這種2MSL等待的另外一個結果是這個TCP鏈接在2MSL等待期間,定義這個鏈接的Socket不能再被使用。這個鏈接只能在2MSL結束後才能再被使用。某些實現和API提供了一種避開這個限制的方法。使用Socket API時,可說明其中的SO_REUSEADDR選項。它將讓調用者對處於2MSL等待的本地端口進行賦值,但咱們將看到TCP原則上仍將避免使用處於2MSL鏈接中的端口。
在FIN_WAIT_2狀態已經發出了FIN,而且另外一端也已對它進行確認。除非實行半關閉,不然將等待另外一端的應用層意識到它已收到一個文件結束符說明,並向咱們發一個FIN來關閉另外一方向的鏈接。只有當另外一端的進程完成這個關閉,咱們這端纔會從FIN_WAIT_2狀態進入TIME_WAIT狀態。
這意味着咱們這端可能永遠保持這個狀態,另外一端也將處於CLOSE_WAIT狀態,並一直保持這個狀態直到應用層決定進行關閉。
如何防止這種FIN_WAIT_2狀態的無限等待?
若是執行主動關閉的應用層將進行全關閉,而不是半關閉來講明它還想接收數據,就設置一個定時器。若是這鏈接空閒超時,則TCP將進入CLOSED狀態。
雙方都執行主動關閉是可能的,TCP協議也容許這樣的同時關閉。
當應用層發生關閉命令時,兩端均從ESTABLISHED變爲FIN_WAIT_1這將致使雙方各發送一個FIN,兩個FIN通過網絡傳送後分別達到另外一端。收到FIN後,狀態有FIN_WAIT_1變爲CLOSEING,併發送最後的ACK。當收到最後的ACK時,狀態變爲TIME_WAIT。同時關閉與正常關閉使用的報文段交換數目相同。
TCP服務器進程是併發的,當一個新的鏈接請求到達服務器時,服務器接受這個請求,並調用一個新進程來處理這個新的客戶請求。
處於LISTEn狀態的服務器進程是當前服務器用於接收客戶鏈接請求,當傳入的鏈接請求達到並被接收時,系統內核中的TCP模塊舊建立一個處於ESTABLISHED狀態的進程。
一個併發服務器調用一個新的進程來處理每一個客戶請求,所以處於被動鏈接請求的服務器應該始終準備處理下一個呼入的鏈接請求。那正是使用併發服務器的根本緣由。但仍有可能出現當服務器在建立一個新的進程時或操做系統正忙於處理優先級更高的進程時,到達多個鏈接請求。當服務器正處於忙時,TCP是如何處理這些呼入的鏈接請求?(1)正等待鏈接請求的一端(服務器)有一個固定長度的鏈接隊列,該隊列中的鏈接已被TCP接受(即三次握手已經完成),但尚未被應用層鎖接受。(2)應用層將指明該隊列的最大長度,這個值一般稱爲積壓值(backlog)。積壓值說明的是TCP監聽的端點已被TCP接受而等待應用層接受的最大鏈接數。它的取值範圍是0~5之間的整數,包括0和5(大多數的應用程序都將這個值說明爲5)。這個積壓值對系統所容許的最大鏈接數或併發服務器所能併發處理的客戶數並沒有影響。(系統最大鏈接數和併發是應用層面的,而backlog是TCP層面的概念)(3)當一個鏈接請求(即SYN)到達時,TCP使用一個算法,根據當前鏈接隊列中的鏈接數來肯定是否接收這個鏈接。(4)若是對於新的鏈接請求,該TCP監聽的端點的鏈接隊列中還有空間。TCP模塊將對SYN進行確認並完成鏈接的創建。但應用層只有在三次握手中的第三個報文段收到後纔會知道這個新鏈接。另外,當客戶進程的主動打開成功但服務器的應用層還不知道這個新的鏈接時,它可能會認爲服務器進程已經準備好接收數據了(若是發生這種狀況,服務器的TCP將接收的數據放入緩衝隊列)。(5)若是對於新的鏈接請求,鏈接隊列中已沒有空間,TCP將不理會收到的SYN,也不發回任何報文段(即不發回RST)。若是應用層不能及時接受已被TCP接受的鏈接,這些鏈接可能佔滿這個鏈接隊列,客戶的主動打開最終將超時。一般隊列盡是因爲應用程序或OS忙形成的,這樣可防止應用程序對傳入的鏈接進行服務。因爲服務器不該答SYN,迫使客戶端TCP隨後重傳SYN,以等待鏈接隊列由空間接受新的鏈接。