socket的accept函數解析

 今天與同窗爭執一個話題:因爲socket的accept函數在有客戶端鏈接的時候產生了新的socket用於服務該客戶端,那麼,這個新的socket到底有沒有佔用一個新的端口?編程

    討論完後,才發現,本身雖然熟悉socket的編程套路,可是卻並非那麼清楚socket的原理,今天就趁這個機會,把有關socket編程的幾個疑問給搞清楚吧。服務器

    先給出一個典型的TCP/IP通訊示意圖。dom

                            

問題一:socket結構體對象到底是怎樣定義的?socket

   咱們知道,在使用socket編程以前,須要調用socket函數建立一個socket對象,該函數返回該socket對象的描述符。async

函數原型:int socket(int domain, int type, int protocol);

   那麼,這個socket對象到底是怎麼定義的呢?它記錄了哪些信息呢?只記錄了本機IP及端口、仍是目的IP及端口、或者都記錄了?函數

   關於這個問題,你們能夠在內核源碼裏面找,也能夠參考這篇文章《struct socket 結構詳解》,咱們能夠看到 socket  結構體的定義以下: 網站

struct socket   
{   
    socket_state              state;   
    unsigned long             flags;   
    const struct proto_ops    *ops;   
    struct fasync_struct      *fasync_list;   
    struct file               *file;   
    struct sock               *sk;   
    wait_queue_head_t         wait;   
    short                     type;   
};

  其中,struct sock 包含有一個 sock_common 結構體,而sock_common結構體又包含有struct inet_sock 結構體,而struct inet_sock 結構體的部分定義以下:spa

struct inet_sock   
{   
    struct sock     sk;   
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)   
    struct ipv6_pinfo   *pinet6;   
#endif   
    __u32           daddr;          //IPv4的目的地址。   
    __u32           rcv_saddr;      //IPv4的本地接收地址。   
    __u16           dport;          //目的端口。   
    __u16           num;            //本地端口(主機字節序)。  
    
…………      
}

    由此,咱們清楚了,socket結構體不只僅記錄了本地的IP和端口號,還記錄了目的IP和端口。code

問題二:connect函數究竟作了些什麼操做?server

   在TCP客戶端,首先調用一個socket()函數,獲得一個socket描述符socketfd,而後經過connect函數對服務器進行鏈接,鏈接成功後,就能夠利用這個socketfd描述符使用send/recv函數收發數據了。

    關於connect函數和send函數的原型以下:

int connect( int sockfd, const struct sockaddr* server_addr, socklen_t addrlen)  
 
int send( int sockfd, const void *msg,int len,int flags);

   那麼,如今的困惑是,爲何send函數僅僅傳入sockfd就能夠知道服務器的ip和端口號? 

   其實,由「問題一」中的答案咱們已經很清楚了,sockfd 描述符所描述的socket對象不只包含了本地IP和端口,同時也包含了服務器的IP和端口,這樣,才能使得send函數只須要傳入sockfd 便可知道該把數據發向什麼地方。而代碼中,目的IP和端口只是在connect函數中出現過,所以,確定是connect函數在成功創建鏈接後,將目的IP和端口寫入了sockfd 描述符所描述的socket對象中。

問題三: accept函數產生的socket有沒有佔用新的端口?

    首先,回顧一下accept函數,原型以下:

int accept(int sockfd, struct sockaddr* addr, socklen_t* len)

    accept函數主要用於服務器端,通常位於listen函數以後,默認會阻塞進程,直到有一個客戶請求鏈接,創建好鏈接後,它返回的一個新的套接字 socketfd_new ,此後,服務器端便可使用這個新的套接字socketfd_new與該客戶端進行通訊,而sockfd 則繼續用於監聽其餘客戶端的鏈接請求。

    至此,個人困惑產生了,這個新的套接字 socketfd_new 與監聽套接字sockfd 是什麼關係?它所表明的socket對象包含了哪些信息?socketfd_new 是否佔用了新的端口與客戶端通訊?

    先簡單分析一番,因爲網站的服務器也是一種TCP服務器,使用的是80端口,並不會因客戶端的鏈接而產生新的端口給客戶端服務,該客戶端依然是向服務器端的80端口發送數據,其餘客戶端依然向80端口申請鏈接。所以,能夠判斷,socketfd_new 並無佔用新的端口與客戶端通訊,依然使用的是與監聽套接字socketfd_new同樣的端口號。

    那這麼說,難道一個端口能夠被兩個socket對象綁定?當客戶端發送數據過來的時候,到底是與哪個socket對象通訊呢?

    我是這麼理解的(歡迎拍磚)。

    首先,一個端口確定只能綁定一個socket。我認爲,服務器端的端口在bind的時候已經綁定到了監聽套接字socetfd所描述的對象上,accept函數新建立的socket對象其實並無進行端口的佔有,而是複製了socetfd的本地IP和端口號,而且記錄了鏈接過來的客戶端的IP和端口號。

    那麼,當客戶端發送數據過來的時候,到底是與哪個socket對象通訊呢?

    客戶端發送過來的數據能夠分爲2種,一種是鏈接請求,一種是已經創建好鏈接後的數據傳輸。因爲TCP/IP協議棧是維護着一個接收和發送緩衝區的。在接收到來自客戶端的數據包後,服務器端的TCP/IP協議棧應該會作以下處理:若是收到的是請求鏈接的數據包,則傳給監聽着鏈接請求端口的socetfd套接字,進行accept處理;若是是已經創建過鏈接後的客戶端數據包,則將數據放入接收緩衝區。這樣,當服務器端須要讀取指定客戶端的數據時,則能夠利用socketfd_new 套接字經過recv或者read函數到緩衝區裏面去取指定的數據(由於socketfd_new表明的socket對象記錄了客戶端IP和端口,所以能夠鑑別)。

相關文章
相關標籤/搜索