為了區分不同應用程序間的網路通訊和連線,主要有3個引數:通訊的目的ip位址、使用的傳輸層協議(tcp 或 udp)和使用的埠號。
socket的原意是「插座」。通過將這3個引數結合起來,與乙個「插座」socket繫結,應用層就可以和傳輸層通過套接字介面,區分來自不同應用程式程序或網路連線的通訊,實現資料傳輸的併發服務。
accept()產生的socket埠號是多少?
要寫網路程式就必須用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了——防火牆一定會阻止其通過的!
實際上,我們可以看到,防火牆並沒有阻止這樣的連線,而且這是最常見的連線請求和處理方式。我不理解的就是,為什麼防火牆沒有阻止這樣的連線?它是如何判斷那條連線是因為connect80埠而生成的?是不是tcp資料報裡有什麼特別的標誌?或者防火牆記住了什麼東西?
後來,我又仔細研讀了tcp/ip的協議棧原理,對很多概念有了更深刻的認識。比如,tcp和udp同屬傳輸層,共同架設在ip層(網路層)之上。而ip層主要負責的是在節點之間(end to end)的資料報傳送,這裡的節點是一台網路裝置,比如計算機。因為ip層只負責把資料送到節點上,而不能區分上面的不同應用,所以tcp和udp協議在其基礎上加入了埠的資訊,埠於是標識的是乙個節點上的乙個應用
。除了增加埠資訊,udp協議基本就沒有對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程式設計介面,所以首先由socekt層來處理)。所謂的accept函式,其實抽象的是tcp的連線建立過程。accept函式返回的新socket其實指代的是本次建立的連線,而乙個連線是包括兩部分資訊的,乙個是源ip和源埠,另乙個宿ip和宿埠。這樣的話,這些socket宿埠就可以都是80!而同時,防火牆的對ip包的處理規則也是清晰明了,不存在前面設想的種種複雜的情形。
明白socket只是對tcp/ip協議棧操作的抽象,而不是簡單的對映關係,這很重要!
昨天和朋友聊了下網路程式設計,關於socket,這裡寫一下我個人的一些理解:)
程式裡可以建立socket,分為普通socket和原始socket兩種型別。
一:普通socket是對tcp/ip協議棧中傳輸層的操作的程式設計介面(一種api)。
有面向連線的流式套接字(sock_stream),屬於針對tcp方式的應用;
有無連線資料報式套接字(sock_dgram),屬於針對udp方式的應用。
對於普通socket,我曾經有個模糊的問題,在多執行緒情況下,伺服器端監聽(listen)某個埠(假設8080)後,每accept乙個客戶端的連線就會產生乙個新的socket。那麼這些新產生的socket的埠是什麼?程式裡肯定沒有指定,那就應該有兩種可能,1:產生隨機埠。2:還是8080埠。第一種假設想了就覺得不可能,防火牆非常有可能會阻止這些隨機埠的包。那麼就是第二種假設了,服務端埠還是8080。但這推翻了我原有的認識,就是「乙個埠被程式占有,其他程式就不能用該埠了」。我覺得其實最有可能的是範圍不同:就是在程式與程式間不能用同一埠,但是在程式內部不同的socket還是可以用同一埠的。所以,為了能夠使「客戶端發給服務端的同一埠(8080)不同執行緒(即不同的socket連線)的包能夠被區分開並進行組合」,必須得有乙個區分包是來自不同連線的顯著特徵,那就是傳輸層包頭裡的源埠了,即乙個socket連線裡客戶端那方的埠。總結一下,對於這種情況,就是傳輸層包頭裡源埠(客戶端)會隨著產生的socket不同,而宿埠相同(伺服器端)。
二:原始socket,建立在網路層上,所以我們可以在傳輸層上構建自己的協議。
如果是自己做個sniffer(網路嗅探器),那麼監聽到的包是來自同一網段的普通socket包(tcp方式或udp方式),所以在程式裡我們要自己寫資料結構(ip頭和tcp或udp頭),並繫結資料。
如果是客戶端和服務端都是由自己用原始socket寫的,那麼可以自己控制協議,像一些網路應用(msn, skype等),可以在網路層往上重寫協議。
同一程序中同一埠如何區分不同的Socket
為了區分不同應用程序間的網路通訊和連線,主要有 3個引數 通訊的目的ip位址 使用的傳輸層協議 tcp 或 udp 和使用的埠號。socket的原意是 插座 通過將這3個引數結合起來,與乙個 插座 socket繫結,應用層就 可以和傳輸層通過套接字介面,區分來自不同應用程式程序或網路連線的通訊,實現...
同一埠如何區分不同的Socket
為了區分不同應用程序間的網路通訊和連線,主要有3個引數 通訊的目的ip位址 使用的傳輸層協議 tcp 或 udp 和使用的埠號。socket的原意是 插座 通過將這3個引數結合起來,與乙個 插座 socket繫結,應用層就可以和傳輸層通過套接字介面,區分來自不同應用程式程序或網路連線的通訊,實現資料...
Nginx配置 同一埠下部署不同專案
1.功能描述 2.實現 3.問題解決 4.最終效果 react專案 1 package.json中增加配置homepage欄位,以網域名稱www.abc.cn為例 2 broserrouter中配置basename屬性 其他引入項省略 引入路由元件 import from react router ...