天天看點

傳輸控制協定(Transmission Control Protocol, TCP) (上)

TCP協定主為了在主機間實作高可靠性的包交換傳輸協定。本文将描述協定标準和實作的一些方法。因為計算機網絡在現代社會中已經是不可缺少的了,TCP協定主要在網絡不可靠的時候完成通信,對軍方可能特别有用,但是對于政府和商用部門也适用。TCP是面向連接配接的端到端的可靠協定。它支援多種網絡應用程式。TCP對下層服務沒有多少要求,它假定下層隻能提供不可靠的資料報服務,它可以在多種硬體構成的網絡上運作。下面的圖是TCP在層次式結構中的位置,它的下層是IP協定,TCP可以根據IP協定提供的服務傳送大小不定的資料,IP協定負責對資料進行分段,重組,在多種網絡中傳送。

TCP的上面就是應用程式,下面是IP協定,上層接口包括一系列類似于作業系統中斷的調用。對于上層應用程式來說,TCP應該能夠異步傳送資料。下層接口我們假定為IP協定接口。為了在并不可靠的網絡上實作面向連接配接的可靠的傳送資料,TCP必須解決可靠性,流量控制的問題,必須能夠為上層應用程式提供多個接口,同時為多個應用程式提供資料,同時TCP必須解決連接配接問題,這樣TCP才能稱得上是面向連接配接的,最後,TCP也必須能夠解決通信安全性的問題。

網絡環境包括由網關(或其它裝置)連接配接的網絡,網絡可以是區域網路也可以是一些城域網或廣域網,但無論它們是什麼,它們必須是基于包交換的。主機上不同的協定有不同的端口号,一對程序通過這個端口号進行通信。這個通信不包括計算機内的I/O操作,隻包括在網絡上進行的操作。網絡上的計算機被看作包傳送的源和目的結點。特别應該注意的是:計算機中的不同程序可能同時進行通信,這時它們會用端口号進行差別,不會把發向A程序的資料由B程序接收的。

程序為了傳送資料會調用TCP,将資料和相應的參數傳送給TCP,于是TCP會将資料傳送到目的TCP那裡,當然這是通過将TCP包打包在IP包内在網絡上傳送達到的。接收方TCP在接收到資料後會通信上層應用程式,TCP會保證接收資料順序的正确性。雖然下層協定可能不會保證順序是正确的。這裡需要說明的是網關在接收到這個包後,會将包解開,看看是不是已經到目的地了,如果沒有到,應該走什麼路由達到目的地,在決定後,網關會根據下一個網絡内的協定情況再次将TCP包打包傳送,如果需要,還要把這個包再次分成幾段再傳送。這個落地檢查的過程是一個耗時的過程。從上面,我們可以看出TCP傳送的基本過程,當然具體過程可能要複雜得多。

在實作TCP的主機上,TCP可以被看成是一個子產品,和檔案系統差別不大,TCP也可以調用一些作業系統的功能,TCP不直接和網絡打交道,控制網絡的任務由專門的裝置驅動子產品完成。TCP隻是調用IP接口,IP向TCP提供所有TCP需要的服務。通過下圖我們可以更清楚地看到TCP協定的結構。

上面已經說過了,TCP連接配接是可靠的,而且保證了傳送資料包的順序,保證順序是用一個序号來保證的。響應包内也包括一個序列号,表示接收方準備好這個序号的包。在TCP傳送一個資料包時,它同時把這個資料包放入重發隊列中,同時啟動記數器,如果收到了關于這個包的确認資訊,将此包從隊列中删除,如果計時逾時則需要重新發送此包。請注意,從TCP傳回的确認資訊并不保證最終接收者接收到資料,這個責任由接收方負責。

每個用于傳送TCP的通道都有一個端口标記,因為這個标記是由每個TCP終端确定的,是以TCP可能不唯一,為了保證這個數值的唯一,要使用網絡位址和端口号的組合達到唯一辨別的目的,我們稱這個為了套接字(Socket),一個連接配接由連接配接兩端的套接字辨別,本地的套接字可能和不同的外部套接字通信,這種通信是全雙工的。

通過向本地端口發送OPEN指令及外部套接字參數建立連接配接,TCP傳回一個标記這個連接配接的名稱,以後如果使用者需要使用這個名稱标記這個連接配接。為了儲存這個連接配接的資訊,我們假設有一個稱為傳輸控制塊(Transmission Control Block,TCB)的東西來儲存。OPEN指令還指定這個連接配接的建立是主動請求還是被動等待請求。下面我們要涉及具體的功能了,TCP段以internet資料報的形式傳送。IP標頭傳送不同的資訊域,包括源位址和目的位址。TCP頭跟在internet標頭後面,提供了一些專用于TCP協定的資訊。下圖是TCP標頭格式圖:

源端口:16位;

目的端口:16位

序列碼:32位,當SYN出現,序列碼實際上是初始序列碼(ISN),而第一個資料位元組是ISN+1;

确認碼:32位,如果設定了ACK控制位,這個值表示一個準備接收的包的序列碼;

資料偏移量:4位,訓示何處資料開始;

保留:6位,這些位必須是0;

控制位:6位;

視窗:16位;

校驗位:16位;

優先指針:16位,指向後面是優先資料的位元組;

選項:長度不定;但長度必須以位元組記;選項的具體内容我們結合具體指令來看;

填充:不定長,填充的内容必須為0,它是為了保證標頭的結合和資料的開始處偏移量能夠被32整除;

 

我們前面已經說過有一個TCB的東西了,TCB裡有存儲了包括發送方,接收方的套接字,使用者的發送和接收的緩沖區指針等變量。除了這些還有一些變量和發送接收序列号有關:

發送序列變量

SND.UNA - 發送未确認

SND.NXT - 發送下一個

SND.WND - 發送視窗

SND.UP - 發送優先指針

SND.WL1 - 用于最後視窗更新的段序列号

SND.WL2 - 用于最後視窗更新的段确認号

ISS - 初始發送序列号

 

接收序列号

RCV.NXT - 接收下一個

RCV.WND - 接收下一個

RCV.UP - 接收優先指針

IRS - 初始接收序列号

下圖會幫助您了解發送序列變量間的關系:

目前段變量

SEG.SEQ - 段序列号

SEG.ACK - 段确認标記

SEG.LEN - 段長

SEG.WND - 段視窗

SEG.UP - 段緊急指針

SEG.PRC - 段優先級

連接配接程序是通過一系列狀态表示的,這些狀态有:LISTEN,SYN-SENT,SYN-RECEIVED,ESTABLISHED,FIN-WAIT-1,FIN-WAIT-2,CLOSE-WAIT,CLOSING,LAST-ACK,TIME-WAIT和 CLOSED。CLOSED表示沒有連接配接,各個狀态的意義如下:

LISTEN - 偵聽來自遠方TCP端口的連接配接請求;

SYN-SENT - 在發送連接配接請求後等待比對的連接配接請求;

SYN-RECEIVED - 在收到和發送一個連接配接請求後等待對連接配接請求的确認;

ESTABLISHED - 代表一個打開的連接配接,資料可以傳送給使用者;

FIN-WAIT-1 - 等待遠端TCP的連接配接中斷請求,或先前的連接配接中斷請求的确認;

FIN-WAIT-2 - 從遠端TCP等待連接配接中斷請求;

CLOSE-WAIT - 等待從本地使用者發來的連接配接中斷請求;

CLOSING - 等待遠端TCP對連接配接中斷的确認;

LAST-ACK - 等待原來發向遠端TCP的連接配接中斷請求的确認;

TIME-WAIT - 等待足夠的時間以確定遠端TCP接收到連接配接中斷請求的确認;

CLOSED - 沒有任何連接配接狀态;

TCP連接配接過程是狀态的轉換,促使發生狀态轉換的是使用者調用:OPEN,SEND,RECEIVE,CLOSE,ABORT和STATUS;傳送過來的資料段,特别那些包括以下标記的資料段SYN,ACK,RST和FIN;還有逾時,上面所說的都會時TCP狀态發生變化。

 

下面的圖表示了TCP狀态的轉換,但這圖中沒有包括錯誤的情況和錯誤處理,不要把這幅圖看成是總說明了。

 

3.3. 序列号

請注意,我們在TCP連接配接中發送的位元組都有一個序列号。因為編了号,是以可以确認它們的收到。對序列号的确認是累積性的,也就是說,如果使用者收到對X的确認資訊,這表示在X以前的資料(不包括X)都收到了。在每個段中位元組是這樣安排的:第一個位元組在標頭後面,按這個順序排列。我們需要認記實際的序列空間是有限的,雖然很大,但是還是有限的,它的範圍是0到2的32次方減1。我想熟悉程式設計的一定知道為什麼要在計算兩個段是不是相繼的時候要使用2的32次方為模了。TCP必須進行的序列号比較操作種類包括以下幾種:

(a) 決定一些發送了的但未确認的序列号;

(b) 決定所有的序列号都已經收到了;

(c) 決定下一個段中應該包括的序列号。

對于發送的資料TCP要接收确認,處理确認時必須進行下面的比較操作:

SND.UNA = 最老的确認了的序列号;

SND.NXT = 下一個要發送的序列号;

SEG.ACK = 接收TCP的确認,接收TCP期待的下一個序列号;

SEG.SEQ = 一個資料段的第一個序列号;

SEG.LEN = 資料段中包括的位元組數;

SEG.SEQ+SEG.LEN-1 = 資料段的最後一個序列号。

請注意下面的關系:

SND.UNA < SEG.ACK =< SND.NXT

如果一個資料段的序列号小于等于确認号的值,那麼整個資料段就被确認了。而在接收資料時下面的比較操作是必須的:

RCV.NXT = 期待的序列号和接收視窗的最低沿;

RCV.NXT+RCV.WND-1 = 最後一個序列号和接收視窗的最高沿;

SEG.SEQ = 接收到的第一個序列号;

SEG.SEQ+SEG.LEN-1 = 接收到的最後一個序列号;

 

上面幾個量有如下關系:

RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND 或 RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND

測試的第一部分是檢查資料段的開始部分是否在接收視窗中,第二部分是檢查資料段的結束部分是否也在接收視窗内;上面兩個檢查通過任何一個就說明它包括視窗要求的資料。實際中的情況會更複雜一些,因為有零視窗和零資料段長,是以我們有下面四種情況:

段長度

接收視窗

測試

SEG.SEQ = RCV.NXT

>0

RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND

>0

不可接受

>0

>0

RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND或RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND

請注意接收視窗的大小可以為零,在視窗為零時它隻用來接收ACK資訊,是以對于一個TCP來說,它可以使用零大小視窗在發送資料的同時接收資料。即使接收視窗的大小為零,TCP必須處理所有接收到資訊的RST和URG域。

我們也應用計數的方式保護了一些特定的控制資訊,這是通過隐式地使用一些控制标記使資料段能夠可靠地重新發送(或确認)為達到的。控制資訊并不在段資料空間中傳送,是以,我們必須采用隐式指定序列号進行控制。SYN和FIN是需要保護的控制量,這兩個控制量也隻在連接配接打開和關閉時使用。SYN被認為是在第一個實際資料之間的資料,而FIN是最後一個實際資料之後的資料。段長度(SEG.LEN)包括資料和序列号空間,如果出現了SYN,那麼SEG.SEQ是SYN的序列号。

初始序列号選擇

協定對于特定連接配接被重複使用沒有什麼限制。連接配接是由一對套接字定義的。新的連接配接執行個體被定義為連接配接的另一次恢複,這就帶來了問題:TCP如果确定多個資料段是從以前連接配接的另一次恢複中取得的呢?這個問題在連接配接迅速打開和關閉,或因為記憶體原因被關閉然後又迅速建立後顯示特别突出。

為了避免混亂,使用者必須避免是以恢複使用某一連接配接,而使序列号發生混亂。我們必須保證序列号的正确性,即使TCP失敗,根本不知道以前的序列号是什麼的情況下也要保證序列号的正确性。當新的連接配接被建立時,産生一個新的初始序列号(ISN)産生子,它用來選擇一個新的32位ISN。産生子和32位時鐘的低度位位元組相關,低位位元組的重新整理頻率大概是4微秒,是以ISN的循環時間大概是4.55小時。是以我們把網絡包的最長生存時間(MSL)小于4.55小時,是以我們可以認為ISN是唯一的。對于每個連接配接都有發送序列号和接收序列号,初始發送序列号(ISS)由發送TCP選擇,而初始接收序列号是在連接配接建立過程中産生的。

對于将要連接配接或初始化的連接配接,兩個TCP必須和對方的初始序列号同步。這通過交換一個控制位SYN和初始序列号完成。我們把帶有SYN的資料段稱為"SYNs"。同步的獲得過程這裡就不重複了,每方必須發送自己的序列号并傳回對對方序列号的确認。

1) A --> B SYN 本方序列号是X

2) A <-- B ACK 本方序列号被确認

3) A <-- B SYN 對方序列号是Y

4) A --> B ACK 确認對方序列号

上面的第2步和第3步可以合并,這時可以成為3階段,是以我們可以稱它為三消息握手。這個過程是必須的,因為序列号不和全局時鐘關聯,TCP也可以有不同的機制選擇ISN。接收到第一個SYN的接收方不可能知道這個資料段是不是被延時,除非它記住了在連接配接上使用的最近的序列号(這通常是不可能的),是以它必須要求發送者确認。

為了保證TCP獲得的确認是剛才發送的段産生的,而不是仍然在網絡中的老資料段産生的,是以TCP必須在MSL時間之内保持沉默。在本文中,我們假設MSL=2小時,這是出于工程的需要,如果使用者覺得可以,他可以改變MSL。請注意如果TCP重新初始化,而記憶體中的序列号正在使用,不需要等待,但必須确認使用的序列号比目前使用的要大。

如果一台主機在未保留任何序列号的情況下失敗,那麼它應該在MSL時間之内不發出任何資料段。下面将會這一情況進行說明。TCP的實作可以不遵守這個規定,但是這會造成老資料被當成新資料接收,而新資料被當成老資料拒絕的情況。

每當資料段形成并進入輸出隊列,TCP會為它指定序列空間中的一個值。TCP中多複本檢測和序列算法都依賴于這個位址空間,在對方發送或接收之前不會超過2的32次方個包存在于輸出隊列中。所有多餘的資料段都會被删除。如果沒有這個規定,會出現多個資料段被指定同一個序列号的情況,會造成混亂。資料段中序列号的多少和資料段中的位元組數一樣多。

在通常情況下,TCP保留下一個要發送的序列号和還未确認的最老的序列号,不要在沒有确認的時候就再次使用,這樣會有些風險,也正是因為這樣的目的,是以序列空間很大。對于2M的網絡,要4.5小時來耗盡序列空間,因為一個資料段可能的最大生存時間也不過十幾分之一秒,這就留下了足夠的空間;而在100M的網絡上需要5.4分鐘,雖然少了點,但也可以了。

如果在實作TCP時沒有為儲存序列号留下空間,那清除多餘的包可能就不能實作了,是以推薦這種類型的TCP實作最好在失敗後等待MSL時間,這樣保證多餘的包被删除。這種情況有時候也可能會出現在保留序列号的TCP實作中。如果TCP在選擇一個另一個TCP連接配接正在使用的序列号時,這台主機突然失敗了,這就産生了問題。這個問題的實質在于主機不知道它失敗了多久,也不知道多餘的複本是不是還在網絡中。

處理這種問題的方法是等待MSL時間,如果不這樣就要冒着對方錯誤接收資料的危險,要等待的時間也就稱為“沉默時間”。實作者可以讓使用者選擇是不是等待,但是無論使用者如何也不見得非要等待MSL時間。