Socket與TCP/IP的關係

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

對於網路程式設計,我們也言必稱 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 協議棧操作的抽象,而不是簡單的對映關係,這很重要!

一個socket 由四個標識決定,客戶端ip,客戶端port, 服務端ip,服務端port,

在一個客戶端和服務端通訊過程中,客戶端ip, 服務端ip,服務端port,都是相同的,只有客戶端port不同,但是隻要一個不同,就算是一個新的socket,

所以accept 返回的是一個新的socket。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/23595918/viewspace-1035313/,如需轉載,請註明出處,否則將追究法律責任。

相關文章