(兩百六十九) TCP/IP詳解筆記-第18章 TCP連接的建立與終止(二)

https://jiatai.blog.csdn.net/article/details/106750898

 

 

18.6 TCP的狀態變遷圖

我們已經介紹了許多有關發起和終止TCP連接的規則。這些規則都能從圖18-12所示的狀態變遷圖中得出。

第18章 TCP連接的建立與終止_TCP/IP詳解卷1 協議_即時通訊網(52im.net)

圖18-12 TCP的狀態變遷圖

這個狀態機看的怪怪的,比如listen狀態切換到syn_rcvd狀態,我可以理解服務端收到了syn狀態切換,不大能理解爲何收到RST是客戶端的正常狀態變遷,這應該是服務端收到客戶端的RST切換到LISTEN啊,這邊理解肯定有問題,明天請教一下,待續

 

在這個圖中要注意的第一點是一個狀態變遷的子集是“典型的”。我們用粗的實線箭頭表示正常的客戶端狀態變遷,用粗的虛線箭頭表示正常的服務器狀態變遷。

第18章 TCP連接的建立與終止183

第二點是兩個導致進入ESTABLISH-ED狀態的變遷對應打開一個連接,而兩個導致從ESTABLISHED狀態離開的變遷主動打開對應關閉一個連接。ESTABLISHED狀態是連接雙方能夠進行雙向數據傳遞的狀態。以後的章節將介紹這個狀態。

將圖中左下角4個狀態放在一個虛線框內,並標爲“主動關閉”。其他兩個狀態(CLOSE_WAIT和LAST_ACK)也用虛線框住,並標爲“被動關閉”。

這個圖中11個狀態的名稱關閉) (CLOSED,LISTEN,SYN_SENT等)是有意與netstat命令顯示的狀態名稱一致。netstat對狀態的命名幾乎與在RFC793中的最初描述一致。CLOSED狀態不是一個真正的狀態,而是這個狀態圖的假想起點和終點。

從LISTEN到SYN_SENT的變遷是正確的,但伯克利版的TCP軟件並不支持它。

只有當SYN_RCVD狀態是從LISTEN狀態(正常情況)進入,而不是從SYN_SENT狀態(同時打開)進入時,從SYN_RCVD回到LISTEN的狀態變遷纔是有效的。這意味着如果我們執行被動關閉(進入LISTEN),收到一個SYN,發送一個帶ACK的SYN(進入SYN_RCVD),然後收到一個RST,而不是一個ACK,便又回到LISTEN狀態並等待另一個連接請求的到來。

圖18-13顯示了在正常的TCP連接的建立與終止過程中,客戶與服務器所經歷的不同狀態。它是圖18-3的再現,不同的是僅顯示了一些狀態。

第18章 TCP連接的建立與終止_即時通訊網(52im.net)

圖18-13 TCP正常連接建立和終止所對應的狀態

假定在圖18-13中左邊的客戶執行主動打開,而右邊的服務器執行被動打開。儘管圖中顯示出由客戶端執行主動關閉,但和早前我們提到的一樣,另一端也能執行主動關閉。

可以使用圖18-12的狀態圖來跟蹤圖18-13的狀態變化過程,以便明白每個狀態的變化。

 

18.6.1 2MSL等待狀態

TIME_WAIT狀態也稱爲2MSL等待狀態。每個具體TCP實現必須選擇一個報文段最大生存時間MSL(Maximum Segment Lifetime)。它是任何報文段被丟棄前在網絡內的最長時間。我們知道這個時間是有限的,因爲TCP報文段以IP數據報在網絡內傳輸,而IP數據報則有限制其生存時間的TTL字段。

RFC 793 [Postel 1981c]指出MSL爲2分鐘。然而,實現中的常用值是30秒,1分鐘,或2分鐘。

從第8章我們知道在實際應用中,對IP數據報TTL的限制是基於跳數,而不是定時器。

對一個具體實現所給定的MSL值,處理的原則是:當TCP執行一個主動關閉,併發回最後一個ACK,該連接必須在TIME_WAIT狀態停留的時間爲2倍的MSL。這樣可讓TCP再次發送最後的ACK以防這個ACK丟失(另一端超時並重發最後的FIN)。

這種2MSL等待的另一個結果是這個TCP連接在2MSL等待期間,定義這個連接的插口(客戶的IP地址和端口號,服務器的IP地址和端口號)不能再被使用。這個連接只能在2MSL結束後才能再被使用。

遺憾的是,大多數TCP實現(如伯克利版)強加了更爲嚴格的限制。在2MSL等待期間,插口中使用的本地端口在默認情況下不能再被使用。我們將在下面看到這個限制的例子。

某些實現和API提供了一種避開這個限制的方法。使用插口API時,可說明其中的SO_REUSEADDR選項。它將讓調用者對處於2MSL等待的本地端口進行賦值,但我們將看到TCP原則上仍將避免使用仍處於2MSL連接中的端口。

 

在連接處於2MSL等待時,任何遲到的報文段將被丟棄。因爲處於2MSL等待的、由該插口對(socket pair)定義的連接在這段時間內不能被再用,因此當要建立一個有效的連接時,來自該連接的一個較早替身(incarnation)的遲到報文段作爲新連接的一部分不可能不被曲解(一個連接由一個插口對來定義。一個連接的新的實例(instance)稱爲該連接的替身)。

我們說圖18-13中客戶執行主動關閉並進入TIME_WAIT是正常的。服務器通常執行被動關閉,不會進入TIME_WAIT狀態。這暗示如果我們終止一個客戶程序,並立即重新啓動這個客戶程序,則這個新客戶程序將不能重用相同的本地端口。這不會帶來什麼問題,因爲客戶使用本地端口,而並不關心這個端口號是什麼。

然而,對於服務器,情況就有所不同,因爲服務器使用熟知端口。如果我們終止一個已經建立連接的服務器程序,並試圖立即重新啓動這個服務器程序,服務器程序將不能把它的這個熟知端口賦值給它的端點,因爲那個端口是處於2MSL連接的一部分。在重新啓動服務器程序前,它需要在1~4分鐘。

可以通過sock程序看到這一切。我們啓動服務器程序,從一個客戶程序進行連接,然後停止這個服務器程序。

第18章 TCP連接的建立與終止_TCP/IP詳解卷1 協議_即時通訊網(52im.net)

當重新啓動服務器程序時,程序報告一個差錯信息說明不能綁定它的熟知端口,因爲該端口已被使用(即它處於2MSL等待)。

運行netstat程序來查看連接的狀態,以證實它的確處於2MSL等待狀態。

如果我們一直試圖重新啓動服務器程序,並測量它直到成功所需的時間,我們就能確定出2MSL值。對於SunOS 4.1.3、SVR4、BSD/386和AIX 3.2.2,它需要1分鐘才能重新啓動服務器程序,這意味着它們的MSL值爲30秒。而對於Solaris 2.2,它需要4分鐘才能重新啓動服務器程序,這表示它的MSL值爲2分鐘。

 

如果一個客戶程序試圖申請一個處於2MSL等待的端口(客戶程序通常不會這麼做),就會出現同樣的差錯。

我們在第1次執行客戶程序時採用-v選項來查看它使用的本地端口爲(11 62)。第2次執行客戶程序時則採用-b選項來選擇端口11 62爲它的本地端口。正如我們所預料的那樣,客戶程序無法那麼做,因爲那個端口是一個還處於2MSL等待連接的一部分。

需要再次強調2MSL等待的一個效果,因爲我們將在第27章的文件傳輸協議FTP中遇到它。和以前介紹的一樣,一個插口對(即包含本地IP地址、本地端口、遠端IP地址和遠端端口的4元組)在它處於2MSL等待時,將不能再被使用。儘管許多具體的實現中允許一個進程重新使用仍處於2MSL等待的端口(通常是設置選項SO_REUSEADDR),但TCP不能允許一個新的連接建立在相同的插口對上。可通過下面的試驗來看到這一點:

第18章 TCP連接的建立與終止_TCP/IP詳解卷1 協議_即時通訊網(52im.net)

第1次運行sock程序中,我們將它作爲服務器程序,端口號爲6666,並從主機bsdi上的一個客戶程序與它連接,這個客戶程序使用的端口爲1098。我們終止服務器程序,因此它將執行主動關閉。這將導致4元組140.252.13.33(本地IP地址)、6666(本地端口號)、140.252.13.35(另一端IP地址)和1098(另一端的端口號)在服務器主機進入2MSL等待。

在第2次運行sock程序時,我們將它作爲客戶程序,並試圖將它的本地端口號指明爲6666,同時與主機bsdi在端口1098上進行連接。但這個程序在試圖將它的本地端口號賦值爲6666時產生了一個差錯,因爲這個端口是處於2MSL等待4元組的一部分。

爲了避免這個差錯,我們再次運行這個程序,並使用選項-A來設置前面提到的SO_REUSEADDR。這將讓sock程序能將它的本地端口號設置爲6666,但當我們試圖進行主動打開時,又出現了一個差錯。即使它能將它的本地端口設置爲6666,但它仍不能和主機bsdi在端口1098上進行連接,因爲定義這個連接的插口對仍處於2MSL等待狀態。

如果我們試圖從其他主機來建立這個連接會如何?首先我們必須在sun上以-A標記來重新啓動服務器程序,因爲它需要的端口(6666)是還處於2MSL等待連接的一部分。

sun % sock -A -s 6666    啓動服務器程序,在端口6666監聽

接着,在2MSL等待結束前,我們在bsdi上啓動客戶程序:

bsdi % sock -b1098 sun 6666

connected on 140.252.13.35.1098 to 140.252.13.33.6666

不幸的是它成功了!這違反了TCP規範,但被大多數的伯克利版實現所支持。這些實現允許一個新的連接請求到達仍處於TIME_WAIT狀態的連接,只要新的序號大於該連接前一個替身的最後序號。在這個例子中,新替身的ISN被設置爲前一個替身最後序號與128 000的和。附錄的RFC 1185 [Jacobsan、Braden和Zhang 1990]指出了這項技術仍可能存在缺陷。

對於同一連接的前一個替身,這個具體實現中的特性讓客戶程序和服務器程序能連續地重用每一端的相同端口號,但這隻有在服務器執行主動關閉纔有效。我們將在圖27-8中使用FTP時看到這個2MSL等待條件的另一個例子。也見習題18.5。

18.6.2 平靜時間的概念

對於來自某個連接的較早替身的遲到報文段,2MSL等待可防止將它解釋成使用相同插口對的新連接的一部分。但這隻有在處於2MSL等待連接中的主機處於正常工作狀態時纔有效。

如果使用處於2MSL等待端口的主機出現故障,它會在MSL秒內重新啓動,並立即使用故障前仍處於2MSL的插口對來建立一個新的連接嗎?如果是這樣,在故障前從這個連接發出而遲到的報文段會被錯誤地當作屬於重啓後新連接的報文段。無論如何選擇重啓後新連接的初始序號,都會發生這種情況。

爲了防止這種情況,RFC 793指出TCP在重啓動後的MSL秒內不能建立任何連接。這就稱爲平靜時間(quiet time)。

只有極少的實現版遵守這一原則,因爲大多數主機重啓動的時間都比MSL秒要長。

18.6.3 FIN_WAIT_2狀態

在FIN_WAIT_2狀態我們已經發出了FIN,並且另一端也已對它進行確認。除非我們在實行半關閉,否則將等待另一端的應用層意識到它已收到一個文件結束符說明,並向我們發一個FIN來關閉另一方向的連接。只有當另一端的進程完成這個關閉,我們這端纔會從FIN_WAIT_2狀態進入TIME_WAIT狀態。

這意味着我們這端可能永遠保持這個狀態。另一端也將處於CLOSE_WAIT狀態,並一直保持這個狀態直到應用層決定進行關閉。

許多伯克利實現採用如下方式來防止這種在FIN_WAIT_2狀態的無限等待。如果執行主動關閉的應用層將進行全關閉,而不是半關閉來說明它還想接收數據,就設置一個定時器。如果這個連接空閒10分鐘75秒,TCP將進入CLOSED狀態。在實現代碼的註釋中確認這個實現代碼違背協議的規範。

 

18.7 復位報文段

我們已經介紹了TCP首部中的RST比特是用於“復位”的。一般說來,無論何時一個報文段發往基準的連接(referenced connection)出現錯誤,TCP都會發出一個復位報文段(這裏提到的“基準的連接”是指由目的IP地址和目的端口號以及源IP地址和源端口號指明的連接。這就是爲什麼RFC 793稱之爲插口)。

 

18.7.1 到不存在的端口的連接請求

產生復位的一種常見情況是當連接請求到達時,目的端口沒有進程正在聽。對於UDP,我們在6.5節看到這種情況,當一個數據報到達目的端口時,該端口沒在使用,它將產生一個ICMP端口不可達的信息。而TCP則使用復位。

產生這個例子也很容易,我們可使用Te lnet客戶程序來指明一個目的端口沒在使用的情況:

bsdi % telnet svr4 20000    端口20000未使用

Trying 140.252.13.34...

telnet: Unable to connect to remote host: Connection refused

Telnet客戶程序會立即顯示這個差錯信息。圖18-14顯示了對應這個命令的分組交換過程。

第18章 TCP連接的建立與終止_即時通訊網(52im.net)

圖18-14 試圖在不存在的端口上打開連接而產生的復位

在這個圖中需要注意的值是復位報文段中的序號字段和確認序號字段。因爲ACK比特在到達的報文段中沒有被設置爲1,復位報文段中的序號被置爲0,確認序號被置爲進入的ISN加上數據字節數。儘管在到達的報文段中沒有真正的數據,但SYN比特從邏輯上佔用了1字節的序號空間;因此,在這個例子中復位報文段中確認序號被置爲ISN與數據長度(0)、SYN比特所佔的1的總和。

18.7.2 異常終止一個連接

我們在18.2節中看到終止一個連接的正常方式是一方發送FIN。有時這也稱爲有序釋放(orderly release),因爲在所有排隊數據都已發送之後才發送FIN,正常情況下沒有任何數據丟失。但也有可能發送一個復位報文段而不是FIN來中途釋放一個連接。有時稱這爲異常釋放(abortive release)。

異常終止一個連接對應用程序來說有兩個優點:(1)丟棄任何待發數據並立即發送復位報文段;(2)RST的接收方會區分另一端執行的是異常關閉還是正常關閉。應用程序使用的API必須提供產生異常關閉而不是正常關閉的手段。

使用sock程序能夠觀察這種異常關閉的過程。Socket API通過“linger on close”選項(SO_LINGER)提供了這種異常關閉的能力。我們加上-L選項並將停留時間設爲0。這將導致連接關閉時進行復位而不是正常的FIN。我們連接到處於服務器上的sock程序,並鍵入一輸入行:

第18章 TCP連接的建立與終止_即時通訊網(52im.net)

圖18-15是這個例子的tcpdump輸出顯示(在這個圖中我們已經刪除了所有窗口大小的說明,因爲它們與討論無關)。

第1~3行顯示出建立連接的正常過程。第4行發送我們鍵入的數據行(12個字符和Unix換行符),第5行是對收到數據的確認。

第18章 TCP連接的建立與終止_TCP/IP詳解卷1 協議_即時通訊網(52im.net)

圖18-15 使用復位(RST)而不是FIN來異常終止一個連接

第6行對應爲終止客戶程序而鍵入的文件結束符(Control_D)。由於我們指明使用異常關閉而不是正常關閉(命令行中的-L0選項),因此主機bsdi端的TCP發送一個RST而不是通常的FIN。RST報文段中包含一個序號和確認序號。需要注意的是RST報文段不會導致另一端產生任何響應,另一端根本不進行確認。收到RST的一方將終止該連接,並通知應用層連接復位。

我們在服務器上得到下面的差錯信息:

第18章 TCP連接的建立與終止_即時通訊網(52im.net)

這個服務器程序從網絡中接收數據並將它接收的數據顯示到其標準輸出上。通常,從它的TCP上收到文件結束符後便將結束,但這裏我們看到當收到RST時,它產生了一個差錯。這個差錯正是我們所期待的:連接被對方復位了。

 

18.7.3 檢測半打開連接

如果一方已經關閉或異常終止連接而另一方卻還不知道,我們將這樣的TCP連接稱爲半打開(Half-Open)的。任何一端的主機異常都可能導致發生這種情況。只要不打算在半打開連接上傳輸數據,仍處於連接狀態的一方就不會檢測另一方已經出現異常。

半打開連接的另一個常見原因是當客戶主機突然掉電而不是正常的結束客戶應用程序後再關機。這可能發生在使用PC機作爲Telnet的客戶主機上,例如,用戶在一天工作結束時關閉PC機的電源。當關閉PC機電源時,如果已不再有要向服務器發送的數據,服務器將永遠不知道客戶程序已經消失了。當用戶在第二天到來時,打開PC機,並啓動新的Telnet客戶程序,在服務器主機上會啓動一個新的服務器程序。這樣會導致服務器主機中產生許多半打開的TCP連接(在第23章中我們將看到使用TCP的keepalive選項能使TCP的一端發現另一端已經消失)。

能很容易地建立半打開連接。在bsdi上運行Telnet客戶程序,通過它和svr4上的丟棄服務器建立連接。我們鍵入一行字符,然後通過tcpdump進行觀察,接着斷開服務器主機與以太網的電纜,並重啓服務器主機。這可以模擬服務器主機出現異常(在重啓服務器之前斷開以太網電纜是爲了防止它向打開的連接發送FIN,某些TCP在關機時會這麼做)。服務器主機重啓後,我們重新接上電纜,並從客戶向服務器發送另一行字符。由於服務器的TCP已經重新啓動,它將丟失復位前連接的所有信息,因此它不知道數據報文段中提到的連接。TCP的處理原則是接收方以復位作爲應答。圖18-16是這個例子的tcpdump輸出顯示(已從這個輸出中刪除了窗口大小的說明、服務類型信息和MSS聲明,因爲它們與討論無關)。

第18章 TCP連接的建立與終止_TCP/IP詳解卷1 協議_即時通訊網(52im.net)

圖18-16是這個例子的tcpdump輸出顯示(已從這個輸出中刪除了窗口大小的說明、服務類型信息和MSS聲明,因爲它們與討論無關)。

第18章 TCP連接的建立與終止_TCP/IP詳解卷1 協議_即時通訊網(52im.net)

圖18-16 復位作爲半打開連接上數據段的應答

第1~3行是正常的連接建立過程。第4行向丟棄服務器發送字符行“hithere”,第5行是確認。

然後是斷開svr4的以太網電纜,重新啓動svr4,並重新接上電纜。這個過程幾乎需要190秒。接着從客戶端輸入下一行(即“another line”),當我們鍵入回車鍵後,這一行被髮往服務器(圖18-16的第6行)。這導致服務器產生一個響應,但要注意的是由於服務器主機經過重新啓動,它的ARP高速緩存爲空,因此需要一個ARP請求和應答(第7、8行)。第9行表示RST被髮送出去。客戶收到復位報文段後顯示連接已被另一端的主機終止(Te lnet客戶程序發出的最後信息不再有什麼價值)。

 

18.8 同時打開

兩個應用程序同時彼此執行主動打開的情況是可能的,儘管發生的可能性極小。每一方必鬚髮送一個SYN,且這些SYN必須傳遞給對方。這需要每一方使用一個對方熟知的端口作爲本地端口。這又稱爲同時打開(simultaneous open)。

例如,主機A中的一個應用程序使用本地端口7777,並與主機B的端口8888執行主動打開。主機B中的應用程序則使用本地端口8888,並與主機A的端口7777執行主動打開。

這與下面的情況不同:主機A中的Telnet客戶程序和主機B中Telnet的服務器程序建立連接,與此同時,主機B中的Telnet客戶程序與主機A的Telnet服務器程序也建立連接。在這個Telnet例子中,兩個Telnet服務器都執行被動打開,而不是主動打開,並且Telnet客戶選擇的本地端口不是另一端Te lnet服務器進程所熟悉的端口。

TCP是特意設計爲了可以處理同時打開,對於同時打開它僅建立一條連接而不是兩條連接(其他的協議族,最突出的是OSI運輸層,在這種情況下將建立兩條連接而不是一條連接)。

當出現同時打開的情況時,狀態變遷與圖18-13所示的不同。兩端幾乎在同時發送SYN,並進入SYN_SENT狀態。當每一端收到SYN時,狀態變爲SYN_RCVD(如圖18-12),同時它們都再發SYN並對收到的SYN進行確認。當雙方都收到SYN及相應的ACK時,狀態都變遷爲ESTABLISHED。圖18-17顯示了這些狀態變遷過程。

第18章 TCP連接的建立與終止_TCP/IP詳解卷1 協議_即時通訊網(52im.net)

圖18-17 同時打開期間報文段的交換

一個同時打開的連接需要交換4個報文段,比正常的三次握手多一個。此外,要注意的是我們沒有將任何一端稱爲客戶或服務器,因爲每一端既是客戶又是服務器。

 

18.9 同時關閉

我們在以前討論過一方(通常但不總是客戶方)發送第一個FIN執行主動關閉。雙方都執行主動關閉也是可能的,TCP協議也允許這樣的同時關閉(simultaneous close)。

在圖18-12中,當應用層發出關閉命令時,兩端均從ESTABLISHED變爲FIN_WAIT_1。這將導致雙方各發送一個FIN,兩個FIN經過網絡傳送後分別到達另一端。收到FIN後,狀態由FIN_WAIT_1變遷到CLOSING,併發送最後的ACK。當收到最後的ACK時,狀態變化爲TIME_WAIT。圖18-19總結了這些狀態的變化。

第18章 TCP連接的建立與終止_TCP/IP詳解卷1 協議_即時通訊網(52im.net)

圖18-19 同時關閉期間的報文段交換

同時關閉與正常關閉使用的段交換數目相同。

 

18.10 TCP選項

TCP首部可以包含選項部分(圖17-2)。僅在最初的TCP規範中定義的選項是選項表結束、無操作和最大報文段長度。在我們的例子中,幾乎每個SYN報文段中我們都遇到過MSS選項。

新的RFC,主要是RFC 1323 [Jacobson,Braden和Borman 1992],定義了新的TCP選項,這些選項的大多數只在最新的TCP實現中才能見到(我們將在第24章介紹這些新選項)。圖18-20顯示了當前TCP選項的格式,這些選項的定義出自於RFC 793和RFC 1323。

第18章 TCP連接的建立與終止_TCP/IP詳解卷1 協議_即時通訊網(52im.net)

圖18-20 TCP選項

每個選項的開始是1字節kind字段,說明選項的類型。kind字段爲0和1的選項僅佔1個字節。其他的選項在kind字節後還有len字節。它說明的長度是指總長度,包括kind字節和len字節。

設置無操作選項的原因在於允許發方填充字段爲4字節的倍數。如果我們使用4.4BSD系統進行初始化TCP連接,tcpdump將在初始的SYN上顯示下面TCP選項:

<mss 512, nop, wscale 0, nop, nop, timestamp 146647 0>

MSS選項設置爲512,後面是NOP,接着是窗口擴大選項。第一個NOP用來將窗口擴大選項填充爲4字節的邊界。同樣,10字節的時間戳選項放在兩個NOP後,佔12字節,同時使兩個4字節的時間戳滿足4字節邊界。

其他kind值爲4、5、6和7的四個選項稱爲選擇ACK及回顯選項。由於回顯選項已被時間戳選項取代,而目前定義的選擇ACK選項仍未定論,並未包括在RFC 1323中,因此圖18-20沒有將它們列出。另外,作爲TCP事務(第24.7節)的T/TCP建議也指明kind爲11,12和13的三個選項。

 

18.11 TCP服務器的設計

我們在1.8節說過大多數的TCP服務器進程是併發的。當一個新的連接請求到達服務器時,服務器接受這個請求,並調用一個新進程來處理這個新的客戶請求。不同的操作系統使用不同的技術來調用新的服務器進程。在Unix系統下,常用的技術是使用fork函數來創建新的進程。如果系統支持,也可使用輕型進程,即線程(thread)。

我們感興趣的是TCP與若干併發服務器的交互作用。需要回答下面的問題:當一個服務器進程接受一來自客戶進程的服務請求時是如何處理端口的?如果多個連接請求幾乎同時到達會發生什麼情況?

18.11.1 TCP服務器端口號

通過觀察任何一個TCP服務器,我們能瞭解TCP如何處理端口號。我們使用netstat命令來觀察Telnet服務器。下面是在沒有Telnet連接時的顯示(只留下顯示Telnet服務器的行)。

第18章 TCP連接的建立與終止_即時通訊網(52im.net)

-a標誌將顯示網絡中的所有主機端,而不僅僅是處於ESTABLISHED的主機端。-n標誌將以點分十進制的形式顯示IP地址,而不是通過DNS將地址轉化爲主機名,同時還要求顯示端口號(例如爲23)而不是服務名稱(如Te lnet)。-f inet選項則僅要求顯示使用TCP或UDP的主機。

顯示的本地地址爲*.23,星號通常又稱爲通配符。這表示傳入的連接請求(即SYN)將被任何一個本地接口所接收。如果該主機是多接口主機,我們將制定其中的一個IP地址爲本地IP地址,並且只接收來自這個接口的連接(在本節後面我們將看到這樣的例子)。本地端口爲23,這是Telnet的熟知端口號。

遠端地址顯示爲*.*,表示還不知道遠端IP地址和端口號,因爲該端還處於LISTEN狀態,正等待連接請求的到達。

現在我們在主機slip(140.252.13.65)啓動一個Te lnet客戶程序來連接這個Te lnet服務器。以下是netstat程序的輸出行:

第18章 TCP連接的建立與終止_即時通訊網(52im.net)

端口爲23的第1行表示處於ESTABLISHED狀態的連接。另外還顯示了這個連接的本地IP地址、本地端口號、遠端IP地址和遠端端口號。本地IP地址爲該連接請求到達的接口(以太網接口,140.252.13.33)。

處於LISTEN狀態的服務器進程仍然存在。這個服務器進程是當前Te lnet服務器用於接收其他的連接請求。當傳入的連接請求到達並被接收時,系統內核中的TCP模塊就創建一個處於ESTABLISHED狀態的進程。另外,注意處於ESTABLISHED狀態的連接的端口不會變化:也是23,與處於LISTEN狀態的進程相同。

現在我們在主機slip上啓動另一個Telnet客戶進程,並仍與這個Telnet服務器進行連接。以下是netstat程序的輸出行:

第18章 TCP連接的建立與終止_即時通訊網(52im.net)

現在我們有兩條從相同主機到相同服務器的處於ESTABLISHED的連接。它們的本地端口號均爲23。由於它們的遠端端口號不同,這不會造成衝突。因爲每個Telnet客戶進程要使用一個外設端口,並且這個外設端口會選擇爲主機(slip)當前未曾使用的端口,因此它們的端口號肯定不同。

這個例子再次重申TCP使用由本地地址和遠端地址組成的4元組:目的IP地址、目的端口號、源IP地址和源端口號來處理傳入的多個連接請求。TCP僅通過目的端口號無法確定那個進程接收了一個連接請求。另外,在三個使用端口23的進程中,只有處於LISTEN的進程能夠接收新的連接請求。處於ESTABLISHED的進程將不能接收SYN報文段,而處於LISTEN的進程將不能接收數據報文段。

下面我們從主機solaris上啓動第3個Telnet客戶進程,這個主機通過SLIP鏈路與主機sun相連,而不是以太網接口。

第18章 TCP連接的建立與終止_即時通訊網(52im.net)

現在第一個ESTABLISHED連接的本地IP地址對應多地址主機sun中的SLIP鏈路接口地址(140.252.1.29)。

18.11.2 限定的本地IP地址

我們來看看當服務器不能任選其本地IP地址而必須使用特定的IP地址時的情況。如果我們爲sock程序指明一個IP地址(或主機名),並將它作爲服務器,那麼該IP地址就成爲處於LISTEN服務器的本地IP地址。例如

sun % sock -s 140.252.1.29 8888

使這個服務器程序的連接僅侷限於來自SLIP接口(140.252.1.29)。netstat的顯示說明了這一點:

第18章 TCP連接的建立與終止_即時通訊網(52im.net)

如果我們從主機solaris通過SLIP鏈路與這個服務器相連接,它將正常工作。

第18章 TCP連接的建立與終止_即時通訊網(52im.net)

但如果我們試圖從以太網(140.252.13)中的主機與這個服務器進行連接,連接請求將被TCP模塊拒絕。如果使用tcpdump來觀察這一切,對連接請求SYN的響應是一個如圖18-21所示的RST。

第18章 TCP連接的建立與終止_TCP/IP詳解卷1 協議_即時通訊網(52im.net)

圖18-21 具有限定本地IP地址服務器對連接請求的拒絕

這個連接請求將不會到達服務器的應用程序,因爲它根據應用程序中指定的本地IP地址被內核中的TCP模塊拒絕。

 

18.11.3 限定的遠端IP地址

在11.12節,我們知道UDP服務器通常在指定IP本地地址和本地端口外,還能指定遠端IP地址和遠端端口。RFC 793中顯示的接口函數允許一個服務器在執行被動打開時,可指明遠端插口(等待一個特定的客戶執行主動打開),也可不指明遠端插口(等待任何客戶)。

遺憾的是,大多數API都不支持這麼做。服務器必須不指明遠端插口,而等待連接請求的到來,然後檢查客戶端的IP地址和端口號。

圖18-22總結了TCP服務器進行連接時三種類型的地址綁定。在三種情況中,lport是服務器的熟知端口,而localIP必須是一個本地接口的IP地址。表中行的順序正是TCP模塊在收到一個連接請求時確定本地地址的順序。最常使用的綁定(第1行,如果支持的話)將最先嚐試,最不常用的(最後一行兩端的IP地址都沒有制定)將最後嘗試。

第18章 TCP連接的建立與終止_TCP/IP詳解卷1 協議_即時通訊網(52im.net)

圖18-22 TCP服務器本地和遠端IP地址及端口號的規範

18.11.4 呼入連接請求隊列

一個併發服務器調用一個新的進程來處理每個客戶請求,因此處於被動連接請求的服務器應該始終準備處理下一個呼入的連接請求。那正是使用併發服務器的根本原因。但仍有可能出現當服務器在創建一個新的進程時,或操作系統正忙於處理優先級更高的進程時,到達多個連接請求。當服務器正處於忙時,TCP是如何處理這些呼入的連接請求?

在伯克利的TCP實現中採用以下規則:

  1. 正等待連接請求的一端有一個固定長度的連接隊列,該隊列中的連接已被TCP接受(即三次握手已經完成),但還沒有被應用層所接受。
    注意區分TCP接受一個連接是將其放入這個隊列,而應用層接受連接是將其從該隊列中移出。
  2. 應用層將指明該隊列的最大長度,這個值通常稱爲積壓值(backlog)。它的取值範圍是0~5之間的整數,包括0和5(大多數的應用程序都將這個值說明爲5)。
  3. 當一個連接請求(即SYN)到達時,TCP使用一個算法,根據當前連接隊列中的連接數來確定是否接收這個連接。我們期望應用層說明的積壓值爲這一端點所能允許接受連接的最大數目,但情況不是那麼簡單。圖18-23顯示了積壓值與傳統的伯克利系統和Solaris2.2所能允許的最大接受連接數之間的關係。注意,積壓值說明的是TCP監聽的端點已被TCP接受而等待應用層接受的最大連接數。這個積壓值對系統所允許的最大連接數,或者併發服務器所能併發處理的客戶數,並無影響。在這個圖中,Solaris系統規定的值正如我們所期望的。而傳統的BSD系統,將這個值(由於某些原因)設置爲積壓值乘3除以2,再加1。

    第18章 TCP連接的建立與終止_TCP/IP詳解卷1 協議_即時通訊網(52im.net)

    圖18-23 對正在聽的端點所允許接受的最大連接數
  4. 如果對於新的連接請求,該TCP監聽的端點的連接隊列中還有空間(基於圖18-23),TCP模塊將對SYN進行確認並完成連接的建立。但應用層只有在三次握手中的第三個報文段收到後纔會知道這個新連接時。另外,當客戶進程的主動打開成功但服務器的應用層還不知道這個新的連接時,它可能會認爲服務器進程已經準備好接收數據了(如果發生這種情況,服務器的TCP僅將接收的數據放入緩衝隊列)。
  5. 如果對於新的連接請求,連接隊列中已沒有空間,TCP將不理會收到的SYN。也不發回任何報文段(即不發回RST)。如果應用層不能及時接受已被TCP接受的連接,這些連接可能佔滿整個連接隊列,客戶的主動打開最終將超時。

通過sock程序能瞭解這種情況。我們調用它,並使用新的選項(-O)。讓它在創建一個新的服務器進程後而沒有接受任何連接請求之前暫停下來。如果在它暫停期間又調用了多個客戶進程,它將導致接受連接隊列被填滿,通過tcpdump能夠看到這一切。

bsdi % sock -s -v -q1 -O30 5555

-q1選項將服務器端的積壓值置1。在這種情況下,傳統的BSD系統中的隊列允許接受兩個連接請求(圖18-23)。-O30選項使程序在接受任何客戶連接之前暫停30秒。在這30秒內,我們可啓動其他客戶進程來填充這個隊列。在主機sun上啓動4個客戶進程。

圖18-24顯示了tcpdump的輸出,首先是第1個客戶進程的第1個SYN(省略窗口大小和MSS聲明。當TCP連接建立時,將客戶進程的端口號用粗體標出)。

端口爲1090的第一個客戶連接請求被TCP接受(報文段1~3)。端口爲1091的第2個客戶連接請求也被TCP接受(報文段4~6)。而服務器的應用仍處於休眠狀態,還未接受任何連接。目前的一切工作都由內核中的TCP模塊完成。另外,兩個客戶進程已經成功地完成了它們的主動打開,因爲它們建立連接的三次握手已經完成。

第18章 TCP連接的建立與終止_TCP/IP詳解卷1 協議_即時通訊網(52im.net)

圖18-24 積壓值例子的tcpdump輸出

我們接着在報文段7(端口1092)和報文段8(端口1093)啓動第3和第4個客戶進程。由於服務器的連接隊列已滿,TCP將不理會兩個SYN。這兩個客戶進程在報文段9,10,11,12,15重發它們的SYN。第4個客戶進程的第3個SYN重傳被接受了,因爲服務器程序的30秒休眠結束後,它將已接受的兩個連接從隊列中移出,使連接隊列變空(服務器程序接收連接的時間是28.19,小於30的原因在於啓動服務器程序後它需要幾秒的時間來啓動第1個客戶進程(報文段1,顯示的就是啓動時間))。第3個客戶進程的第4個SYN重傳這時將被接受(報文段15~17)。服務器程序先接受第4個客戶連接(端口1093)的原因是服務器程序30秒休眠與客戶程序重傳之間的定時交互作用。

我們期望接收連接隊列按先進先出順序傳遞給應用層。如TCP接受了端口爲1090和1091的連接,我們希望應用層先接受端口爲1090的連接,然後再接受端口爲1091的連接。但許多伯克利的TCP實現都出現按後進先出的傳遞順序,這個錯誤已存在了多年。產商最近已開始改正這個錯誤,但在如SunOS 4.13等系統中仍存在這個問題。

當隊列已滿時,TCP將不理會傳入的SYN,也不發回RST作爲應答,因爲這是一個軟錯誤,而不是一個硬錯誤。通常隊列已滿是由於應用程序或操作系統忙造成的,這樣可防止應用程序對傳入的連接進行服務。這個條件在一個很短的時間內可以改變。但如果服務器的TCP以系統復位作爲響應,客戶進程的主動打開將被廢棄(如果服務器程序沒有啓動我們就會遇到)。由於不應答SYN,服務器程序迫使客戶TCP隨後重傳SYN,以等待連接隊列有空間接受新的連接。

這個例子中有一個巧妙之處,這在大多TCP/IP的具體實現中都能見到,就是如果服務器的連接隊列未滿時,TCP將接受傳入的連接請求(即SYN),但並不讓應用層瞭解該連接源於何處(即不告知源IP地址和源端口)。這不是TCP所要求的,而只是共同的實現技術(如伯克利源代碼通常都這麼做)。如果一個API如TLI(見1.15節)嚮應用程序提供瞭解連接請求的到來的方法,並允許應用程序選擇是否接受連接。當應用程序假定被告知連接請求已經到來時,TCP的三次握手已經結束!其他運輸層的實現可能將連接請求的到達與接受分開(如OSI的運輸層),但TCP不是這樣。

Solaris 2.2提供了一個選項使TCP只有在應用程序說可以接受(tcp_eager_listeners見E.4),才允許接受傳入的連接請求。

這種行爲也意味着TCP服務器無法使客戶進程的主動打開失效。當一個新的客戶連接傳遞給服務器的應用程序時,TCP的三次握手就結束了,客戶的主動打開已經完全成功。如果服務器的應用程序此時看到客戶的IP地址和端口號,並決定是否爲該客戶進行服務,服務器所能做的就是關閉連接(發送FIN),或者復位連接(發送RST)。無論哪種情況,客戶進程都認爲一切正常,因爲它的主動打開已經完成,並且已經向服務器程序發送過請求。

 

18.12 小結

兩個進程在使用TCP交換數據之前,它們之間必須建立一條連接。完成後,要關閉這個連接。本章已經詳細介紹瞭如何使用三次握手來建立連接以及使用4個報文段來關閉連接。

我們用tcpdump程序顯示了TCP首部中的各個字段。也瞭解了連接建立是如何超時,連接復位是如何發送,使用半打開連接發生的情況以及TCP是如何提供半關閉、同時打開和同時關閉。

弄清TCP操作的關鍵在於它的狀態變遷圖。我們跟蹤了連接建立與關閉的步驟以及它們的狀態變遷過程。還討論了在設計TCP併發服務器時TCP連接建立的具體實現方法。

一個TCP連接由一個4元組唯一確定:本地IP地址、本地端口號、遠端IP地址和遠端端口號。無論何時關閉一個連接,一端必須保持這個連接,我們看到TIME_WAIT狀態將處理這個問題。處理的原則是執行主動打開的一端在進入這個狀態時要保持的時間爲TCP實現中規定的MSL值的兩倍。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章