1. 程式人生 > >套接字疑惑------------套接字的資料結構是什麼樣子的???

套接字疑惑------------套接字的資料結構是什麼樣子的???

一、套接字與套接字介面

套接字是應用程式訪問系統網路服務的介面。端到端的通訊通過一對套接字來實現,一個套接字對應一個通訊端點。

從實現來看,套接字是端端通訊的抽象描述。在應用程式裡,套接字對應一個整數值(套接字描述符);在核心裡,套接字對應一個管理通訊過程的物件(struct socket結構)。該結構與前面所說的整數值一一對應。

在Linux系統核心中,struct socket結構物件不僅封裝了管理通訊過程的資料資訊,還封裝了負責網路通訊的功能函式(其具體的實現方式有些類似MFC中的回撥函式)。為了便於訪問這些功能函式,Linux提供了一系列程式設計介面函式,即套接字介面。通過這些介面,應用程式觸發系統呼叫來呼叫上述功能函式,從而訪問網路服務。

根據底層網路機制的差異,套接字可以定義為不同協議族的套接字。比如INET協議族套接字,UNIX域套接字等。在核心原始檔include/linux/socket.h中,每個協議族識別符號被定義為一個整數:

   1: #define PF_UNSPEC    AF_UNSPEC
   2: #define PF_UNIX      AF_UNIX
   3: #define PF_LOCAL     AF_LOCAL
   4: #define PF_INET      AF_INET
   5: #define PF_AX25      AF_AX25
   6: ...

建立套接字時,可以通過引數選擇協議族。如果指定為PF_INET協議族,則稱套接字為INET套接字。INET套接字的介面函式提供了TCP/IP網路服務功能。

套接字介面會實現一系列功能以便應用程式使用,包括:套接字建立、地址繫結、連線請求、埠監聽、連線請求允許、資料包傳送和接收等。

二、套接字建立流程

應用程式採用socket函式建立套接字。socket會觸發核心呼叫sys_socket函式,隨後sys_socket又呼叫sock_create函式。根據socket函式在引數中指定的協議族型別sock_create函式有選擇地呼叫不同的套接字建立函式,例如,當指定PF_INET協議族時,sock_create函式呼叫inet_create建立INET套接字。這些套接字函式首先建立套接字的核心表示結構,再返回一個套接字描述符來標識生成的套接字物件。socket函式返回時,應用程式獲得這個套接字描述符。

三、一些重要的套接字資料結構

Linux核心提供了一系列管理套接字的資料型別,此處只介紹幾個比較重要的。其中,struct net_proto_family是協議族管理型別,負責不同協議族套接字的建立;struct socket是套接字結構型別,每個套接字在核心中都對應唯一的struct sockt結構;struct proto_ops是協議族套接字的操作集,統一管理套接字操作函式;struct sock是套接字在傳輸層的表示型別,為套接字指定傳輸層協議後,其struct socket的sk指標將指向一個與傳輸協議關聯的struct sock結構;struct proto是傳輸層協議操作集,統一管理傳輸層協議的操作函式。

1、struct net_proto_family

   1: struct net_proto_family
   2: {
   3:     int family;                                          //協議族標誌
   4:     int (*create)(strcut socket *sock, int protocol);    //套接字建立方法
   5:     short authentication;                                //認證管理欄位
   6:     short encryption;                                    //加密管理欄位
   7:     short encrypt_net;
   8:     struct module * owner;
   9: };

struct net_proto_family管理不同協議族套接字的建立方法,其中create指標指向具體協議族套接字的建立函式。在include/linux/socket.h檔案中,核心用整數定義這些協議族。在初始化時,Linux系統支援的協議族被註冊到陣列static struct net_proto_family *net_families中。以下為一些常見協議族

   1: #define PF_UNIX            1        //UNIX域協議族
   2: #define PF_INET            2        //TCP/IP協議族
   3: #define PF_IPX             4        //Novell網的IPX協議族
   4: #define PF_APPLETALK       5
   5: #define PF_ATMPVC          8
   6: #define PF_X25            9
   7: #define PF_INET6          10
   8: #define PF_NETLINK        16

Linux通過net_families表來管理協議族,該表是struct net_proto_family型別的指標陣列,定義為:

   1: static struct net_proto_family * net_families[NPROTO];

協議族初始化時,套接字建立方法被sock_register函式(位於net\socket.c中)註冊到net_families中:

   1: int sock_register(const struct net_proto_family *ops)
   2: {
   3:     ...
   4:     if (rcu_dereference_protected(net_families[ops->family],
   5:                       lockdep_is_held(&net_family_lock)))
   6:         err = -EEXIST;
   7:     else {
   8:         rcu_assign_pointer(net_families[ops->family], ops);
   9:         err = 0;
  10:     }
  11:     ...
  12: }

比如INET協議族初始化時,函式inet_init呼叫sock_register來註冊INET套接字的建立方法(struct net_proto_family型別變數inet_family_ops管理INET套接字的建立方法)。

sock_register函式定義如下:

   1: int sock_register(const struct net_proto_family *ops)
   2: {
   3:     int err;
   4:  
   5:     if (ops->family >= NPROTO) {
   6:         printk(KERN_CRIT "protocol %d >= NPROTO(%d)\n", ops->family,
   7:                NPROTO);
   8:         return -ENOBUFS;
   9:     }
  10:  
  11:     spin_lock(&net_family_lock);
  12:     if (rcu_dereference_protected(net_families[ops->family],
  13:                       lockdep_is_held(&net_family_lock)))
  14:         err = -EEXIST;
  15:     else {
  16:         rcu_assign_pointer(net_families[ops->family], ops);
  17:         err = 0;
  18:     }
  19:     spin_unlock(&net_family_lock);
  20:  
  21:     printk(KERN_INFO "NET: Registered protocol family %d\n", ops->family);
  22:     return err;
  23: }

例如,inet_family_ops變數定義為:

   1: struct net_proto_family inet_family_ops =
   2: {
   3:     .family = PF_INET,
   4:     .create = inet_create,
   5:     .owner = THIS_MODULE
   6: };

變數inet_family_ops管理INET協議族套接字的建立方法,其create指標指向inet_create函式。

上文提到的inet_init函式程式碼如下:

   1: static int __init inet_init(void)
   2: {
   3:     (void)sock_register(&inet_family_ops);
   4: }

struct net_proto_family的定義程式碼位於檔案include/linux/net.h

2、struct proto_ops

   1: struct proto_ops
   2: {
   3:     int family;//協議族
   4:     struct module *owner;//所屬模組
   5:     //以下均為以函式指標形式指定的套接字操作
   6:     int (*release)(struct socket *sock);
   7:     int (*bind)(struct socket *sock, struct sockaddr *myaddr,
   8:                 int sockaddr_len);
   9:     ...
  10: };

struct proto_ops型別是協議族操作集。不同協議族套接字的操作函式可能不同,但Linux通過struct proto_ops中那些函式指標統一了介面,用這些函式指標來操作套接字。對於INET協議族的TCP和UDP協議,Linux分別提供了inet_stream_ops和inet_dgram_ops兩個struct proto_ops型別的變數,以inet_stream_ops為例:

   1: struct proto_ops inet_stream_ops = 
   2: {
   3:     .family = PF_INET,
   4:     .owner = THIS_MODULE,
   5:     .release = inet_release,
   6:     .bind = inet_bind,
   7:     ...
   8: }

struct proto_ops的宣告位於include/linux/net.h

3、struct socket型別

   1: struct socket
   2: {
   3:     socket_state    state;//狀態值
   4:     unsigned long flags;//標識
   5:     struct proto_ops *ops;//指向一個struct proto_ops結構,為套接字提供協議族操作集
   6:     struct fasync_struct *fasync_list;//非同步喚醒連結串列
   7:     struct file *file;//檔案指標
   8:     struct sock *sk;//指向struct sock結構體,該結構體是套接字在傳輸層的表示結構
   9:     wait_queue_head_t wait;//等待佇列
  10:     short type;//傳輸層資料型別
  11:     unsigned char passcred;//授權描述
  12: };

struct socket型別統一表示不同協議族的套接字,它與應用程式引用的套接字描述符一一對應。成員ops指向struct proto_ops結構體,代表具體協議族套接字的操作集。比如若INET協議族採用TCP傳輸協議,那麼ops指向的結構體為inet_stream_ops;若INET協議族套接字採用UDP傳輸協議,那麼ops指向的結構體等同於inet_dgram_ops。sk指向的struct sock結構體代表了傳輸層的套接字結構,包含了與具體傳輸層協議相關的資訊,如其中的sk_prot指標提供了傳輸層的操作集。

struct socket的定義程式碼位於檔案include/linux/net.h

4、struct proto型別

   1: struct proto
   2: {
   3:     void (*close)(struct sock *sk, long timeout);//關閉套接字
   4:     ...//一系列函式指標
   5:     atomic_t *memory_allocated;//分配的記憶體數
   6:     atomic_t *sockets_allocated;//分配的套接字數
   7:     int *memory_pressure;//與memory_allocated有關的控制
   8:     int *sysctl_mem;//記憶體訪問限制指標
   9:     int *sysctl_wmem;
  10:     int *sysctl_rmem;
  11:     int max_header;//最大頭部
  12:     char name[32];//名字描述
  13:     struct
  14:     {
  15:         int inuse;
  16:         u8 __pad[SMP_CACHE_BYTES - sizeof(int)];
  17:     }stats[NR_CPUS];//填充欄位
  18: };

該結構體封裝了傳輸協議操作集。

5、struct sock型別

該結構體是套接字在傳輸層的表示結構。所有的套接字最後通過該結構來使用網路協議棧的服務。這個結構體定義於include/net/sock.h。

6、struct net_protocol型別

該結構體定義於include/net/protocol.h。

該結構管理傳輸層接收資料包的方法(也是通過函式指標)。例如TCP初始化時,核心在前文提到過的inet_init函式中註冊了接收TCP資料包的方法用(struct net_protocol型別變數tcp_protocol表示),程式碼如下

   1: if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
   2:     printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n");

7、struct inet_protosw型別

該結構把INET套接字的協議族操作集與傳輸層協議操作集關聯起來。

   1: struct inet_protosw {
   2:     struct list_head list;
   3:  
   4:         /* These two fields form the lookup key.  */
   5:     unsigned short     type;       /* This is the 2nd argument to socket(2). */
   6:     unsigned short     protocol; /* This is the L4 protocol number.  */
   7:  
   8:     struct proto     *prot;//傳輸層協議操作集
   9:     const struct proto_ops *ops;//協議族套接字操作集
  10:   
  11:     char             no_check;   /* checksum on rcv/xmit/none? */
  12:     unsigned char     flags;      /* See INET_PROTOSW_* below.  */
  13: };

在/net/ipv4/Af_inet.c中分別針對INET套接字中TCP、UDP和RAW三種協議,以陣列方式定義了三個inet_protosw型別變數:

   1: static struct inet_protosw inetsw_array[] =
   2: {
   3:     {
   4:         .type =       SOCK_STREAM,
   5:         .protocol =   IPPROTO_TCP,
   6:         .prot =       &tcp_prot,
   7:         .ops =        &inet_stream_ops,
   8:         .no_check =   0,
   9:         .flags =      INET_PROTOSW_PERMANENT |
  10:                   INET_PROTOSW_ICSK,
  11:     },
  12:  
  13:     {
  14:         .type =       SOCK_DGRAM,
  15:         .protocol =   IPPROTO_UDP,
  16:         .prot =       &udp_prot,
  17:         .ops =        &inet_dgram_ops,
  18:         .no_check =   UDP_CSUM_DEFAULT,
  19:         .flags =      INET_PROTOSW_PERMANENT,
  20:        },
  21:  
  22:  
  23:        {
  24:            .type =       SOCK_RAW,
  25:            .protocol =   IPPROTO_IP,    /* wild card */
  26:            .prot =       &raw_prot,
  27:            .ops =        &inet_sockraw_ops,
  28:            .no_check =   UDP_CSUM_DEFAULT,
  29:            .flags =      INET_PROTOSW_REUSE,
  30:        }
  31: };

這三個變數實現了INET協議族中各協議族操作集與對應的傳輸層操作的關聯。在inet_init函式初始化INET協議族時,通過inet_register_protosw把陣列inetsw_array記錄的資訊註冊到inetsw陣列中,在系統實際使用時,以協議族為索引訪問inetsw。

struct inet_protosw位於include/net/protocol.h

8、Msghdr型別

該結構負責應用程式與核心互動網路資料,準備傳送的資料資訊封裝在這裡。該結構體位於include/linux/socoket.h

四、套接字建立流程及資料傳送流程

以INET協議族為例:

image

套接字建立流程:

應用程式建立套接字時,呼叫socket函式,該函式觸發核心的sys_socket函式。sys_socket函式引用sock_create,sock_create又呼叫__sock_create來建立套接字結構,具體實現包括:struct socket結構物件,訪問net_families變數得到協議族套接字的建立方法。對於INET套接字得到對應的套接字建立方法inet_create。inet_create函式建立一個INET套接字(用socket結構體表示),並根據套接字型別來決定傳輸層所採用的協議。socket結構體最重要的資訊保存於其內部的sock結構體中(由sk指標指向該結構),inet_create為此結構分配記憶體,並根據套接字型別做出不同的初始化