SSL/TLS的原理以及互聯網究竟是如何工作的(8)——奇妙的傳輸層!

我:都一個多月沒來這裡了呢,這次去拜訪一下誰呢……

TCP&UDP:啊,幽靈來了啊!你都很久沒來了啊,最近很忙是嗎?

我:最近事情是比較多……你們兩位是?

TCP:我是傳輸層的TCP(Transmission Control Protocol,傳輸控制協議)!旁觀這位是同樣大名鼎鼎的UDP(User Datagram Protocol,用戶數據報協議),你應該都聽說過吧?

我:那是自然。你是面向連接的協議,而UDP是無連接的協議;你是可靠的,而UDP是不可靠的;你們的上層協議就是那些應用層協議,你們是應用程序進程和操作系統之間的橋樑……

TCP:不錯啊,幽靈,我記得一開始你來這裡的時候還什麼都不知道呢,結果鬧了不少笑話。看來真是“士別三日,應當刮目相看”呢(笑)。不過……你知道為什麼嗎?

我:什麼為什麼?

TCP:我是說,你知道為什麼我要通過三路握手建立連接呢?為什麼我和UDP是應用程序進程和操作系統之間的橋樑呢?為什麼我釋放連接的時候也要進行三路握手呢(不過也可以通過RST直接單方面釋放連接)?

我:……這還真不知道。

TCP:那我還是從頭開始說吧。傳輸層的定位在網絡層和應用層之間。網絡層及以下層都是對用戶隱藏的(用戶無法直接與之交互),而且獨立於應用程序進程工作;而應用層則是直接面對用戶的,應用層協議都是配合對應的具體應用程序進程進行工作的,例如HTTP協議配合瀏覽器進程,XMPP協議配合IRC(Internet Relay Chat)進程,SMTP配合郵件客戶端進程。工作在傳輸層的軟件或硬件被稱作transport entity(傳輸實體),負責實現進程間通信。

而我所在的傳輸層,一方面不受具體應用程序的制約,另一方面又能和用戶直接交互外加對用戶屏蔽下層工作細節,從而實現遠程進程之間的端到端通信。要做到這一切,就必須處在應用程序進程和操作系統之間。

對了,說到進程之間的端到端通信,這裡要引入一個重要概念:SAP(Service Access Point,服務訪問點)。要在兩個遠程進程間建立連接,就必須要設置SAP。傳輸層的SAP是端口,被稱作TSAP(Transport Service Access Point,傳輸服務訪問點);而網絡層的SAP是IP地址,被稱作NSAP(Network Service Access Point,網絡服務訪問點)。IP地址只能定位到主機,加上端口之後才能定位到進程,進而實現端到端通信。

然後呢,網絡編程中的一個重要人物就要出場了!

Socket:現在是我的專場!我叫socket(套接字),專門負責進程之間的遠程通信,程序員們最熟悉我了(笑)。不管是建立連接還是維持連接還是釋放連接,都需要我出馬才行啊:服務器通過LISTEN開始被動監聽,準備建立連接;客戶端發出CONNECT命令,主動要求建立連接;服務器接收到命令之後就ACCEPT,連接建立了!然後呢,客戶端發送RECEIVE命令,要求收到指定的數據;服務器收到之後就SENT,把指定數據發送出去;最後,服務器和客戶端都發送CLOSE命令,釋放連接。

這些命令都是通過socket實現的,而且所有的socket都引用了前面提到的SAP以訪問對應的進程。

我:快頭暈了……

TCP:別急,下面就開始說有意思的事了。你有沒有想過,三路握手其實很麻煩啊?為什麼不能直接就建立連接呢?比如說直接發送連接建立請求和數據過去,然後馬上就處理好了,幹嘛還要對方確認再正式建立連接呢?

我:是為了保證可靠吧……具體我也不清楚

TCP:並不是為了保證可靠。可靠的含義是:1,先發送的先到達;2,出現丟包和損壞等數據沒被正常送到的情況時,保證重傳。
第一點是通過建立虛電路實現的,一個連接只有一條路由路徑;而要實現第二點也很簡單,一方傳輸數據和連接請求過去之後要求對方發送確認信息即可。

可是,還有一個很嚴重的問題沒有解決:假設一個客戶端給服務器發送了帶著數據的連接請求,但是卻因為網絡擁塞而超時了;那麼此時客戶端會重新發送之前的請求,這次沒有超時,成功建立了連接。

問題在於,一段時間之後最開始的那個請求也到達了,結果就是服務器沒法識別出這是雷同請求,然後又建立連接了!

我:這會造成什麼嚴重後果嗎?

TCP:會造成極為嚴重的後果!想像一下這樣一個場景:客戶端向銀行服務器發送請求,要求向一個賬戶轉入一萬美元;結果發生了前面所說的狀況,那麼最終結果就是轉入了兩萬美元,但客戶端卻一無所知!

我:這聽起來真夠糟糕的。不過,可以給每個連接都加上獨一無二的序列號啊?

TCP:聽起來不錯,問題在於……服務器出於性能上的考慮,並不會專門建立一個序列號數據庫來記住那些連接的不同序列號的。

我:(汗)那就只能先不發送數據,只發送連接建立請求,然後服務器發送確認信息,客戶端再發送數據;這樣一來,後來雷同請求來了之後,服務器端就會先問客戶端是不是真的想要建立連接,然後客戶端就會發現異常從而拒絕了。

TCP:沒錯,就是這樣!這一過程就是我的三路握手(three-way handshake)!當然實現的時候就沒這麼簡單了,但基本思路就是這樣的!

我:哈哈,被我猜對了啊!

TCP:接下來該說說怎樣釋放連接了。如果對方主機長時間無響應或者斷網了抑或是出現了其他異常狀況,另一方就會直接發送RST包單方面釋放連接。

但是在正常情況下,當通信完成時,我並不會單方面釋放連接的。

我:為什麼呢?

TCP:想像一下,一個客戶端發送了最後一個請求,然後服務器響應之後就單方面釋放連接了,可是響應數據包丟失了。那麼,客戶端就會請求重傳,但此時服務器已經釋放了連接,不會再理會客戶端了。

我:那麼這數據就永遠丟失了!

TCP:是啊。不過呢,釋放連接比建立連接要難弄的多了。我先說一個故事吧:有一個山谷,山谷裡駐紮著一隻三人軍隊,叫A隊;兩邊的山頂上分別駐紮著B隊的兩個分隊,每個分隊各兩人。B隊想要消滅A隊,但是如果分隊單獨行動,那麼一定會輸的。

我:那就想辦法同時行動啊。

TCP:這是自然。問題在於,B隊的兩個分隊要想取得聯繫,只能派斥候(斥候沒有戰鬥力,不算分隊成員)走過山谷去通知對方。而斥候是有可能被抓的,也就是說不保證消息能送到。在無法確定對方是否收到消息的情況下,不管哪個分隊都不會行動的。

我:那麼,在分隊1的斥候到達分隊2把消息帶來之後,分隊2再派斥候把確認消息帶過去就行了啊?

TCP:真的嗎?帶確認消息的斥候也是有可能被抓的!如果說斥候被抓了,那還不是沒法確定對方是否收到消息?

我:那就再派斥候把確認消息收到的確認消息傳回去(這話說起來怎麼這麼繞口)……

TCP:還是不行啊!確認消息的確認消息還是有可能送不到啊。最關鍵的一點在於,最後一個斥候是否到達是無法被確認的;那麼送出最後一個斥候的分隊就會猶豫對方是不是收到了確認信息,會不會同時攻擊A隊。一旦這麼猶豫了,分隊就不會參加攻擊行動,那麼也就無法戰勝A隊了。

現在,試著把斥候換成計算機網絡中的不可靠通信信道,把攻擊命令換成連接釋放命令吧。

我:這麼說實際上根本就做不到一起釋放連接了?

TCP:不,實際情況反倒好辦很多:還是拿前面的例子,在完成最後一次數據傳遞後,客戶端發送FIN包,服務器端接收到之後發送FIN+ACK包,同時釋放連接;客戶端接收到ACK包或者長時間接收不到響應,都會釋放連接。

我:相當於不管不顧了。不過既然本來就是個無解的問題,那也只能這麼辦了。

TCP:啊,這次扯了太多了,對不起,UDP你只能下次再出場了。

UDP:好吧,T!C!P!

科普文鏈接集合:
https://plus.google.com/+GhostAssassin/posts/a8aKzvZLsuV

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *