十 C# Socket協議的形象描述

weixin_34344677發表於2009-11-12
socket的英文原義是“孔”或“插座”。在這裡作為4BDS UNIX的程式通訊機制,取後一種意義。socket非常類似於電話插座。以一個國家級電話網為例。電話的通話雙方相當於相互通訊的2個程式,區號是它的網路地址;區內一個單位的交換機相當於一臺主機,主機分配給每個使用者的局內號碼相當於socket號。任何使用者在通話之前,首先要佔有一部電話機,相當於申請一個socket;同時要知道對方的號碼,相當於對方有一個固定的socket。然後向對方撥號呼叫,相當於發出連線請求(假如對方不在同一區內,還要撥對方區號,相當於給出網路地址)。對方假如在場並空閒(相當於通訊的另一主機開機且可以接受連線請求),拿起電話話筒,雙方就可以正式通話,相當於連線成功。雙方通話的過程,是一方向電話機發出訊號和對方從電話機接收訊號的過程,相當於向socket傳送資料和從socket接收資料。通話結束後,一方掛起電話機相當於關閉socket,撤消連線。

    在電話系統中,一般使用者只能感受到本地電話機和對方電話號碼的存在,建立通話的過程,話音傳輸的過程以及整個電話系統的技術細節對他都是透明的,這也與socket機制非常相似。socket利用網間網通訊設施實現程式通訊,但它對通訊設施的細節毫不關心,只要通訊設施能提供足夠的通訊能力,它就滿足了。

    至此,我們對socket進行了直觀的描述。抽象出來,socket實質上提供了程式通訊的端點。程式通訊之前,雙方首先必須各自建立一個端點,否則是沒有辦法建立聯絡並相互通訊的。正如打電話之前,雙方必須各自擁有一臺電話機一樣。在網間網內部,每一個socket用一個半相關描述:

(協議,本地地址,本地埠)

      一個完整的socket有一個本地唯一的socket號,由作業系統分配。

   最重要的是,socket 是面向客戶/伺服器模型而設計的,針對客戶和伺服器程式提供不同的socket 系統呼叫。客戶隨機申請一個socket (相當於一個想打電話的人可以在任何一臺入網電話上撥號呼叫),系統為之分配一個socket號;伺服器擁有全域性公認的 socket ,任何客戶都可以向它發出連線請求和資訊請求(相當於一個被呼叫的電話擁有一個呼叫方知道的電話號碼)。

   socket利用客戶/伺服器模式巧妙地解決了程式之間建立通訊連線的問題。伺服器socket 半相關為全域性所公認非常重要。讀者不妨考慮一下,兩個完全隨機的使用者程式之間如何建立通訊?假如通訊雙方沒有任何一方的socket 固定,就好比打電話的雙方彼此不知道對方的電話號碼,要通話是不可能的。

   -----

    Socket 介面是訪問 Internet 使用得最廣泛的方法。 如果你有一臺剛配好TCP/IP協議的主機,其IP地址是202.120.127.201, 此時在另一臺主機或同一臺主機上執行ftp 202.120.127.201,顯然無法建立連線。因"202.120.127.201" 這臺主機沒有執行FTP服務軟體。同樣, 在另一臺或同一臺主機上執行瀏覽軟體 如Netscape,輸入"http://202.120.127.201",也無法建立連線。現在,如果在這臺主機上執行一個FTP服務軟體(該軟體將開啟一個Socket, 並將其繫結到21埠),再在這臺主機上執行一個Web 服務軟體(該軟體將開啟另一個Socket,並將其繫結到80埠)。這樣,在另一臺主機或同一臺主機上執行ftp 202.120.127.201,FTP客戶軟體將通過21埠來呼叫主機上由FTP 服務軟體提供的Socket,與其建立連線並對話。而在netscape中輸入"http://202.120.127.201"時,將通過80埠來呼叫主機上由Web服務軟體提供的Socket,與其建 立連線並對話。

    在Internet上有很多這樣的主機,這些主機一般執行了多個服務軟體,同時提供幾種服務。每種服務都開啟一個Socket,並繫結到一個埠上,不同的埠對應於不同的服務。Socket正如其英文原意那樣,象一個多孔插座。一臺主機猶如佈滿各種插座的房間,每個插座有一個編號,有的插座提供220伏交流電, 有的提供110伏交流電,有的則提供有線電視節目。 客戶軟體將插頭插到不同編號的插座,就可以得到不同的服務。


 
    TCP/IP協議和Socket的關係
    Socket就是TCP/IP協議的一種實現,它封裝了TCP/IP協議並提供了一組基於該協議的操作方法。或者是:socket用來實現應用程式程式和tcp/ip的通訊。
    

    要寫網路程式就必須用 Socket ,這是程式設計師都知道的。而且,面試的時候,我們也會問對方會不會 Socket 程式設計?一般來說,很多人都會說, Socket 程式設計基本就是 listen accept 以及 send write 等幾個基本的操作。是的,就跟常見的檔案操作一樣,只要寫過就一定知道。

    對於網路程式設計,我們也言必稱 TCP/IP ,似乎其它網路協議已經不存在了。對於 TCP/IP ,我們還知道 TCP UDP ,前者可以保證資料的正確和可靠性,後者則允許資料丟失。最後,我們還知道,在建立連線前,必須知道對方的 IP 地址和埠號。除此,普通的程式設計師就不會知道太多了,很多時候這些知識已經夠用了。最多,寫服務程式的時候,會使用多執行緒來處理併發訪問。

    我們還知道如下幾個事實:

    1:一個指定的埠號不能被多個程式共用。比如,如果 IIS 佔用了 80 埠,那麼 Apache 就不能也用 80 埠了。

    2:很多防火牆只允許特定目標埠的資料包通過。

    3:服務程式在 listen 某個埠並 accept 某個連線請求後,會生成一個新的 socket 來對該請求進行處理。

 

    於是,一個困惑了我很久的問題就產生了。如果一個 socket 建立後並與 80 埠繫結後,是否就意味著該 socket 佔用了 80 埠呢?如果是這樣的,那麼當其 accept 一個請求後,生成的新的 socket 到底使用的是什麼埠呢(我一直以為系統會預設給其分配一個空閒的埠號)?如果是一個空閒的埠,那一定不是 80 埠了,於是以後的 TCP 資料包的目標埠就不是 80 -- 防火牆一定會組織其通過的!實際上,我們可以看到,防火牆並沒有阻止這樣的連線,而且這是最常見的連線請求和處理方式。我的不解就是,為什麼防火牆沒有阻止這樣的連線?它是如何判定那條連線是因為 connet80 埠而生成的?是不是 TCP 資料包裡有什麼特別的標誌?或者防火牆記住了什麼東西?

 

    後來,我又仔細研讀了 TCP/IP 的協議棧的原理,對很多概念有了更深刻的認識。比如, TCP UDP 同屬於傳輸層,共同架設在 IP 層(網路層)之上。而 IP 層主要負責的是在節點之間( End to End )的資料包傳送,這裡的節點是一臺網路裝置,比如計算機。因為 IP 層只負責把資料送到節點,而不能區分上面的不同應用,所以 TCP UDP 協議在其基礎上加入了埠的資訊,埠於是標識的是一個節點上的一個應用。除了增加埠資訊, UPD 協議基本就沒有對 IP 層的資料進行任何的處理了。而 TCP 協議還加入了更加複雜的傳輸控制,比如滑動的資料傳送視窗( Slice Window ),以及接收確認和重發機制,以達到資料的可靠傳送。不管應用層看到的是怎樣一個穩定的 TCP 資料流,下面傳送的都是一個個的 IP 資料包,需要由 TCP 協議來進行資料重組。

 

    所以,我有理由懷疑,防火牆並沒有足夠的資訊判斷 TCP 資料包的更多資訊,除了 IP 地址和埠號。而且,我們也看到,所謂的埠,是為了區分不同的應用的,以在不同的 IP 包來到的時候能夠正確轉發。

 

    TCP/IP 只是一個協議棧,就像作業系統的執行機制一樣,必須要具體實現,同時還要提供對外的操作介面。就像作業系統會提供標準的程式設計介面,比如 Win32 程式設計介面一樣, TCP/IP 也必須對外提供程式設計介面,這就是 Socket 程式設計介面 -- 原來是這麼回事啊!

 

    在 Socket 程式設計介面裡,設計者提出了一個很重要的概念,那就是 socket 。這個 socket 跟檔案控制程式碼很相似,實際上在 BSD 系統裡就是跟檔案控制程式碼一樣存放在一樣的程式控制程式碼表裡。這個 socket 其實是一個序號,表示其在控制程式碼表中的位置。這一點,我們已經見過很多了,比如檔案控制程式碼,視窗控制程式碼等等。這些控制程式碼,其實是代表了系統中的某些特定的物件,用於在各種函式中作為引數傳入,以對特定的物件進行操作 -- 這其實是 C 語言的問題,在 C++ 語言裡,這個控制程式碼其實就是 this 指標,實際就是物件指標啦。

 

    現在我們知道, socket TCP/IP 並沒有必然的聯絡。 Socket 程式設計介面在設計的時候,就希望也能適應其他的網路協議。所以, socket 的出現只是可以更方便的使用 TCP/IP 協議棧而已,其對 TCP/IP 進行了抽象,形成了幾個最基本的函式介面。比如 create listen accept connect read write 等等。

 

    現在我們明白,如果一個程式建立了一個 socket ,並讓其監聽 80 埠,其實是向 TCP/IP 協議棧宣告瞭其對 80 埠的佔有。以後,所有目標是 80 埠的 TCP 資料包都會轉發給該程式(這裡的程式,因為使用的是 Socket 程式設計介面,所以首先由 Socket 層來處理)。所謂 accept 函式,其實抽象的是 TCP 的連線建立過程。 accept 函式返回的新 socket 其實指代的是本次建立的連線,而一個連線是包括兩部分資訊的,一個是源 IP 和源埠,另一個是宿 IP 和宿埠。所以, accept 可以產生多個不同的 socket ,而這些 socket 裡包含的宿 IP 和宿埠是不變的,變化的只是源 IP 和源埠。這樣的話,這些 socket 宿埠就可以都是 80 ,而 Socket 層還是能根據源 / 宿對來準確地分辨出 IP 包和 socket 的歸屬關係,從而完成對 TCP/IP 協議的操作封裝!而同時,放火牆的對 IP 包的處理規則也是清晰明瞭,不存在前面設想的種種複雜的情形。

    明白 socket 只是對 TCP/IP 協議棧操作的抽象,而不是簡單的對映關係,這很重要!

 

相關文章