1. 程式人生 > >accept()返回的套接字繫結哪個埠 新舊套接字的聯絡

accept()返回的套接字繫結哪個埠 新舊套接字的聯絡

摘要:對於伺服器程式設計中最重要的一步等待並接受客戶的連線,那麼這一步在程式設計中如何完成,accept函式就是完成這一步的。它從核心中取出已經建立的客戶連線,然後把這個已經建立的連線返回給使用者程式,此時使用者程式就可以與自己的客戶進行點到點的通訊了。

accept函式等待並接受客戶請求:

#include<sys/socket.h>
int accept(int sockfd, struct sockaddr* addr, socklen_t* len)
返回:非負描述字——成功, -1——失敗

accept預設會阻塞程序,直到有一個客戶連線建立後返回,它返回的是一個新可用的套接字,這個套接字是連線套接字。此時我們需要區分兩種套接字,一種套接字正如accept的引數sockfd,它是監聽套接字,在呼叫listen函式之後,一個套接字會從主動連線的套接字變身為一個監聽套接字;而accept返回是一個連線套接字,它代表著一個網路已經存在的點點連線。自然要問的是:為什麼要有兩種套接字?原因很簡單,如果使用一個描述字的話,那麼它的功能太多,使得使用很不直觀,同時在核心確實產生了一個這樣的新的描述字。


引數sockfd 
引數sockfd就是上面解釋中的監聽套接字,這個套接字用來監聽一個埠,當有一個客戶與伺服器連線時,它使用這個一個埠號,而此時這個埠號正與這個套接字關聯。當然客戶不知道套接字這些細節,它只知道一個地址和一個埠號。 
引數addr 
這是一個結果引數,它用來接受一個返回值,這返回值指定客戶端的地址,當然這個地址是通過某個地址結構來描述的,使用者應該知道這一個什麼樣的地址結構。如果對客戶的地址不感興趣,那麼可以把這個值設定為NULL。 
引數len 
如同大家所認為的,它也是結果的引數,用來接受上述addr的結構的大小的,它指明addr結構所佔有的位元組個數。同樣的,它也可以被設定為NULL。

如果accept成功返回,則伺服器與客戶已經正確建立連線了,此時伺服器通過accept返回的套接字來完成與客戶的通訊。

這周同學們在做網路程式設計的時候,碰到一個監聽套接字的問題,在這裡大概描述一下:

比如我的程式開了一個監聽埠,與客戶端建立連線之後,生成了一個新套接字。這時我執行了只關閉監聽埠的語句,結果卻發現監聽埠和已建立的連線仍然存在。我都已經關閉了監聽套接字,為什麼客戶端還可以繼續往監聽埠發信息?這到底是因為什麼呢?新套接字和監聽套接字有什麼關係呢?

比如,你開了80監聽埠,有一個客戶連線你accept了,這時關閉80埠。但此時客戶端發信息的時候必然是發向80斷口,但是80已經關了啊,但是通訊依然正常進行。其實我剛接觸套接字的時候也是認為所有從客戶端發來的資料都需要經過監聽套接字轉一下才能收到。所有的初學者都容易犯這個誤解。

經過一段時間的使用,我現在是明白了,監聽套接字就是個牽線指路的,你實質上是跟它指的那個人說話。因為你要找的那個人不可能隨時等你來,而監聽套接字就是專職等你來問,它回答你要找的人在哪,並喚醒你要找的人,於是通話就建立起來了,就像現實生活中的接線員一樣。

也就是說,在連線建立後,客戶端用發出連線的那個SOCKET向伺服器發資料,是發給伺服器新建立的SOCKET,而不是伺服器的監聽SOCKET。伺服器的監聽SOCKET永遠只是用來接受連線請求。

這就好比你去吃飯,飯館門口有迎賓小姐(監聽SOCKET)看到你來後和你打招呼,然後(ACCEPT)找來一個新的服務員(NEW SOCKET)來接待你,然後守在門口繼續監聽下一個。監聽的小姐走了,接待你的服務員當然不受影響。

說到這裡有必要說一下accept()函式。以下是《Linux網路程式設計》一書,第六章 Berkeley套接字對accept()函式的描述:

函式 accept()有一些難懂。當呼叫它的時候,大致過程是下面這樣的:

● 有人從很遠很遠的地方嘗試呼叫 connect()來連線你的機器上的某個埠(當然是你已經在 listen()的)。

● 他的連線將被 listen 加入等待佇列等待 accept()函式的呼叫(加入等待佇列的最多數目由呼叫 listen()函式的第二個引數 backlog 來決定)。

● 你呼叫 accept()函式,告訴他你準備連線。

● accept()函式將回返回一個新的套接字描述符,這個描述符就代表了這個連線!

好,這時候你有了兩個套接字描述符,返回給你的那個就是和遠端計算機的連線,而第一個套接字描述符仍然在你的機器上原來的那個埠上 listen()。

這時候你所得到的那個新的套接字描述符就可以進行 send()操作和recv()操作了。

通過上面的解釋,相信您一定已經對監聽套接字有了進一步的瞭解了吧!


為了區分不同應用程序間的網路通訊和連線,主要有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等),可以在網路層往上重寫協議。


先說一下自己的觀點   不知是否正確 高手指正 我覺得騰訊 這個題目 似乎 有點問題啊
accept 好像可以發生在3次握手之前  也可以發生在3次握手之後。。。。。 
  
不信 可以試試 
1.沒有accept   3次握手照樣成功。也就是說你可以等到3次握手成功以後,再呼叫accept 
  
2.你也可以先呼叫accept,後面再開始3次握手,但是,這種情況下,accept會阻塞直到3次握手成功為止。 ------------------------------------------------------------------------------------------------------------------------------------------------------- 網友評論: accept發生在三次握手之後。

第一次握手:客戶端傳送syn包(syn=j)到伺服器。
第二次握手:伺服器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也傳送一個ASK包(ask=k)。
第三次握手:客戶端收到伺服器的SYN+ACK包,向伺服器傳送確認包ACK(ack=k+1)。
三次握手完成後,客戶端和伺服器就建立了tcp連線。這時可以呼叫accept函式獲得此連線。 ------------------------------------------------------------