傳送層的功能
在前面討論網際網路層的時候﹐我們知道﹕網際網路層協定只提供路由資訊的判斷﹐以確定封包的傳送路徑。但事實上 IP 協定只確保封包交換設備之間的傳輸﹐并沒有提供一套機制來確保數據的傳輸。在低層的通訊里﹐封包可能在傳送過程中發生錯誤﹐諸如網路硬體的損壞﹑網路負荷過重等等﹐導致封包被丟棄或損壞。由于封包路由的多樣性和復雜性﹐以及影響路由因素眾多及其不可預測性﹐封包之抵達常是不依序的﹐或是會發生重復傳送的情形。因此﹐我們必須提供一套網路技術﹐以達成更可靠和有效的傳送。
再者﹐ IP 封包的體積是有限的﹐然而﹐網路程式之間交換的數據往往會超過這個體積限制﹔那么﹐我們必須有另一套機制將程式送來的資料進行規劃﹐以符合 IP 封包的傳送要求。在高層的程式里﹐除非利用非可靠和非連線型(connectionless)的資料傳送方式﹐否則,程式設計者必須對每一個一個應用程式處理偵錯和修復的動作﹐這無疑增加了程式設計和修改的難度﹐而且也做成許多重復的處理動作。因此﹐我們也有必要找出一個可靠的資料流傳送方法﹐以建立單獨且適用于所有應用程式的資料傳送協定。這樣就可以將應用程式與網路內部協定隔離﹐同時提供一致的資料流傳送界面。
傳送層的設計可以說是應上述要求而生的﹐它的主要功能有﹕
· 接管由上層協定傳來的資料﹐并以 IP 封包可以接受的格式進行“封裝”工作。
· 進行資料傳送和回應的確認﹐以及處理資料流的檢測和控制。
· 對不同的連線進行追蹤及轉換。
在 TCP/IP 協定組中,關于傳送層的協定就是 TCP 和 UDP 了﹐我們將在下面詳細討論。簡而言之﹐TCP 提供的是一個可靠的資料流傳送服務﹔相對而言﹐UDP 提供的是一個非可靠的非連線型(connectionless)的資料流傳送服務。
可靠性傳送服務的特性
在應用程式對 TCP 的可靠性傳送服務之主要要求有五個﹕
· 資料流導向。處理程式之間的大量資料傳送﹐確保雙方的位元資料流之統一性。
· 虛擬電路連接。建立和回應資料流傳送的連線請求﹐并驗證傳送期間的資料﹐同時對通訊進行偵錯。
· 緩沖處理。如果程式送出的資料太小﹐協定將等到收集到一定大小的資料包之后才進行傳送﹐然而協定允許“push”機制強行送出。
· 非結構化資料流。應用程式在建立連線之前﹐要先了解資料流動內容與格式﹐方能使用資料流服務。
· 全雙工連線。允許雙向性的資料傳送﹐各自被視為互不相關的獨立資料流。然而﹐它提供了返回資料流中攜帶傳送控制資訊的機制。
TCP 協定在進行傳輸的時侯,必須依靠 IP 協定傳送封包。相對于 TCP , IP 協定屬于不可靠協定﹐因為兩個協定必需同時困綁工作,因此只要其一能做到可靠傳輸就可以了。要詳細的描述 TCP 如何提供可靠性傳送是非常復雜的﹐但大部分可靠性協定都采用一定的確認機制來保證傳送之可靠性。這種技術需要接收端以確認信息(Acknowledgement) 回應發送端﹐肯定資料無誤的到達﹐同時雙方保留傳送的封包記錄﹐以作下一筆資料的確認依據。此外﹐還利用定時器的機制﹐以在傳送逾時后重新發送封包,以確保資料的完整性。我們可以從下圖中看到確認機制的簡單模式﹕
發送端在送出封包之后﹐會起始一個專門針對該封包的計時器﹐當下層網路延遲過久導致封包不能按預估時間獲得接收端的確認信息﹐那么發送端會認為該封包可能在傳送過程中丟失﹐然后會重新發送該封包、并同時重設計時器﹔如果封包的確認信息在逾時前被接收到﹐則取消該封包的計時器﹐以進行下一封包的傳送。
計時器雖然解決了封包丟失的問題﹐但如果封包的抵達只是因為網路延遲的關系沒有在預定時間完成﹐但卻在發送端重發后抵達﹐那么﹐接收端就有可能接收到重復的封包。為解決這個困繞﹐傳送協定會為每一個封包分配一個序號﹐并要求接收端按封包序號傳回確認信息。這樣﹐當接收端收到封包的時候﹐則可以依據序號判斷封包是否被重復傳送,同時也能正確的重組資料順序﹔而發送端也能根據確認封包的序號來判斷封包是否被正確接收。
滑動視窗(Sliding Window)
從剛才介紹的可靠性傳送知識作一個推斷:假如每一個單一封包都需要需要等待前面的封包確認之后才進行傳送的話﹐將會導致整個連線過程時間的增加﹐同時也會造成頻寬的浪費。假如在低速的網路上面﹐或設備延遲﹐甚至還會造成網路處于空閑狀態。有鑒于此﹐聰明的傳送層協定設計者們引入了一個滑動視窗的概念。
我們可以將滑動視窗理解為多重發送和多重確認的技術。它允許發送端在接收到確認信息之前同時傳送多個封包﹐因而能夠更充份的利用網路頻寬和加速資料傳送速度。滑動視窗的操作可以想象為下圖﹕
我們利用 Sliding Window 在收發兩端各劃分出一個緩沖范圍(buffer)﹐定義了多大的資料量可被打包傳送。在連線建立起來之初﹐兩端都會將 window 的設定值還原到初始值﹐比方說﹕ 3 個封包。發送端一次過發送三個封包出去﹐如果接收端夠順利﹐也能一次處理接收下來的三個封包的話﹐就會向發送端確認全部三個封包,并告知接收端之 window 值為 3 。然后,發送端視窗則會往后移動三個封包﹐填補發送出去之封包的空缺。但如果接收端太忙﹐或是其它因素影響﹐暫時只能處理兩個封包﹐那么﹐在視窗里面就剩下一個封包﹐然后就會告訴發送端 window 值為 2。這個時候﹐發送端就只送出兩個封包﹐而視窗就會往后移動兩個封包﹐填補發送出去的空缺。因此,視窗的大小是不固定的,這就是為什么我們會在視窗前面加上“滑動”字眼的原因了。(注意:這里使用封包數目作 window size 是不正確的,僅作例子參考而已。實際上的單位應是位元組,視不同的作業系統而各有不同,一般為 4096 bytes,但也有擴展至 16384 bytes 的。而且視窗的 size 是每個確認封包都不同的,端視當前的緩沖區狀況,其機制比前述復雜許多。) #p#分頁標題#e#
在啟動滑動視窗之后﹐封包的傳送看起來如下圖﹕
滑動視窗會記住哪些封包已經被確認﹐并且為每一個未被確認的封包保留各自的計時器。如果在逾時后還沒得到該封包的確認﹐則重發該封包。發送端在移動視窗的時候﹐它會移過所有已確認的封包。在視窗中﹐編號最低的封包﹐往往是序列中的第一個未被確認的封包。
通訊埠口(port)
大多數的作業系統都提供多工環境﹐允許多個應用程式同時執行﹐在系統術語里面﹐我們管每一個程式的起止為一個行程。每一個行程都是動態產生的﹐發送端無法預知接收端的某一個行程的實際狀況如何。那么﹐當一個封包抵達目的地之后﹐接收如何將封包交給正確的行程處理呢﹖
在傳送層協定里面﹐我們為程式產生的行程分配一個通訊埠口﹐其值為一個正正數。當一個應用程式需要建立網路連線的時候﹐傳送層協定就為該應用程式產生的行程建立一個埠口。而事實上,所謂的網路連線,就是兩個通訊埠口之間的連線。關于網路通訊模式的建立有兩種﹕
· 主動連線
· 被動連線
主動連線是當埠口建立之后﹐行程透過該埠口主動發出連線的要求﹔被動模式則是﹐當埠口建立之后﹐行程在該埠口等待連線的請求。在 client/server 的架構之下,連線的建立順序通常是伺服器端先建立好被動連線﹐然后等待客戶端的主動連線。
在技術上﹐行程使用哪一個埠口并不重要﹐關鍵是能讓對方知道埠口是哪一個就行。我們可以把 IP 位址看成主機的門牌號碼﹐而埠口則是服務柜臺。在多工的環境下﹐行程會在一個門牌上面開啟多個柜臺。您或許會問:由起始端主動發起之連線封包抵達之后﹐它究竟憑什么來判斷究竟哪個柜臺才是正確的行程呢?在日常的生活中﹐大不了逐個柜臺去問... 然而在網路系統上面﹐這個似乎有點不切實際。因為﹐每一個埠口的建立和關閉都是隨機的﹐在不同的時段里﹐所開啟的埠口數目和號碼都不盡相同。既然如此﹐等待連線那端何不先將接收行程所使用埠口號碼告知起始端呢?但問題是:既然連線要由起始端主動建立才能連上等待端﹐在沒有真正連上之前如何得知呢?不是雞生蛋、蛋生雞的問題嗎?
有見及此﹐在網際網路的實作應用中﹐人們將一些常用的服務程式所使用埠口號碼固定起來。例如﹕21 給 FTP 服務使用﹑23 給 TELNET 服務使用﹑25 給 SMTP 服務使用... 我們稱這樣的埠口為 Well-Known Port。在伺服器端﹐這些常用服務會先行建立好被動連線﹐打開所分配的埠口﹐以等待起始端的連線請求。那么﹐起始端只要在封包填上目的端的埠口值﹐接收端就能將封包傳給正確的服務了, 這也就是透過約定俗成的分配來建立連線。但事實上,您大可架設一個地下網站,故意使用其它非 Well-Known Port 來建立被動連線埠口,這樣,只有那些事先被告知埠口值、且能修改主動連線設定的客戶端才知門而入了。


