zoukankan      html  css  js  c++  java
  • struct socket结构体详解

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://weiguozhihui.blog.51cto.com/3060615/1585297

        在内核中为什么要有struct socket结构体呢?

       struct socket结构体的作用是什么?

       下面这个图,我觉得可以回答以上两个问题。  wKioL1R8EgfAF5ZDAAGd1S8RhIA115.jpg

        由这个图可知,内核中的进程可以通过使用struct socket结构体来访问linux内核中的网络系统中的传输层、网络层、数据链路层。也可以说struct socket是内核中的进程与内核中的网路系统的桥梁。

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    struct socket
    {
         socket_state  state; // socket state
          
         short   type ; // socket type
          
         unsigned long  flags; // socket flags
          
         struct fasync_struct  *fasync_list;
          
         wait_queue_head_t wait;
          
         struct file *file;
          
         struct sock *sock;  // socket在网络层的表示;
          
         const struct proto_ops *ops;
               
    }
     
     
    struct socket结构体的类型
    enum sock_type
    {
       SOCK_STREAM = 1, // 用于与TCP层中的tcp协议数据的struct socket
        
       SOCK_DGRAM  = 2, //用于与TCP层中的udp协议数据的struct socket
        
       SOCK_RAW    = 3, // raw struct socket
        
       SOCK_RDM    = 4, //可靠传输消息的struct socket
        
       SOCK_SEQPACKET = 5,// sequential packet socket
        
       SOCK_DCCP   = 6,
        
       SOCK_PACKET = 10, //从dev level中获取数据包的socket
    };
     
    struct socket 中的flags字段取值:
      #define SOCK_ASYNC_NOSPACE  0
      #define SOCK_ASYNC_WAITDATA 1
      #define SOCK_NOSPACE        2
      #define SOCK_PASSCRED       3
      #define SOCK_PASSSEC        4


       我们知道在TCP层中使用两个协议:tcp协议和udp协议。而在将TCP层中的数据往下传输时,要使用网络层的协议,而网络层的协议很多,不同的网络使用不同的网络层协议。我们常用的因特网中,网络层使用的是IPV4和IPV6协议。

       所以在内核中的进程在使用struct socket提取内核网络系统中的数据时,不光要指明struct socket的类型(用于说明是提取TCP层中tcp协议负载的数据,还是udp层负载的数据),还要指明网络层的协议类型(网络层的协议用于负载TCP层中的数据)。

       linux内核中的网络系统中的网络层的协议,在linux中被称为address family(地址簇,通常以AF_XXX表示)或protocol family(协议簇,通常以PF_XXX表示)。

       wKiom1R8Hx3THEffAAVlvwxDKzk034.jpg

     wKioL1R8H7vSc54pAAN3Efqa5N0077.jpg        

    wKiom1R8H9Lz9dl4AAMbicmeYGI140.jpg

    wKioL1R8IG2DYTRwAAKqw4_sJrE248.jpg

     

     1.创建一个struct socket结构体:

       int sock_create(int family, int type, int protocol, 

                        struct socket **res);

       int sock_create_kern(int family, int type, int protocol,

                             struct socket **res);

       EXPROT_SYMBOL(sock_create);

       EXPROT_SYMBOL(sock_create_kern);

       family : 指定协议簇的类型,其值为:PF_XXX或 AF_XXX

       type   : 指定要创建的struct socket结构体的类型;

       protocol : 一般为0;

       res    : 中存放创建的struct socket结构体的地址;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    int sock_create(int family, int type, int protocol, struct socket **res)
    {
       return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
    }
     
    int sock_create_kern(int family, int type, int protocol, struct socket **res)
    {
       return __sock_create( &init_net, family, type, protocot, res, 1 );
    }
    如果在内核中创建struct socket时,推荐使用sock_create_kern()函数;
     
     
    // 网络协议簇结构体
    struct net_proto_family
    {
       int family ; // 协议簇
       int (*create)(struct net *net, struct socket *sock,  int protocol);
       struct module  *owner;
    };
     
    内核中的所有的网络协议的响应的网络协议簇结构体都存放在 net_families[]指针数组中;
    static struct net_proto_family *net_families[NPROTO];
     
    static int __sock_create(struct net *net, int family, int type, int protocol, 
                             struct socket **res, int kern )
    {
        struct socket *sock;
        struct net_proto_family *pf;
         
        sock = sock_alloc();//分配一个struct socket 结构体
        sock->type = type;
         
        pf = rcu_dereference(net_families[family]); //获取相应的网络协议簇结构体的地址;
        pf->create(net, sock, protocol); // 对struct socket结构体做相应的处理;
         
        *res = sock; // res中保存创建的struct socket结构体的地址;
        return 0;
    }
     
     
    struct socket_alloc
    {
       struct socket socket ;
       struct inode vfs_node ;
    }
     
    static inline struct socket *SOCKET_I(struct inode *inode)
    {
       return &contain_of(inode, struct socket_alloc, vfs->node)->socket;
    }
    static struct socket *sock_alloc(void)
    {
         struct inode *inode;
         struct socket *sock;
         inode = new_inode(sock_mnt->mnt_sb);//分配一个新的struct inode节点
         sock = SOCKET_I(inode); 
         inode->i_mode = S_IFSOCK | S_IRWXUGO;//设置inode节点的权限
         inode->i_uid = current_fsuid(); // 设置节点的UID
         inode->i_gid = current_fsgid(); //设置节点的GID
          
         return sock; 
    }


      有以上的代码可知:linux内核在使用sock_create()、sock_create_kern()

    进行struct socket结构体的创建时,其本质是分配了一个struct socket_alloc

    结构体,而这个struct socket_alloc结构体中包含了struct socket 和struct

    inode(struct inode结构体,是linux内核用来刻画一个存放在内存中的文件的,通过将struct inode 和 struct socket绑定在一起形成struct socket_alloc结构体,来表示内核中的网络文件)。然后对分配的struct socket结构体进行初始化,来定义内核中的网络文件的类型(family, type, protocol).

       在linux网络系统中还有两个非常重要的套接字地址结构体:

              struct sockaddr_in

              struct sockaddr;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    typedef unsigned short sa_family_t;
     
    // Internet Address
    struct in_addr{
        __b32 s_addr;
    }
     
    //struct describing an Internet socket address
    //sockaddr_in 中存放端口号、网路层中的协议类型(ipv4,ipv6)等,网络层的IP地址;
    struct sockaddr_in
    {
        sa_family_t sin_family ; // Address family AF_XXX
         
        __be16      sin_port   ; // 端口号
         
        struct in_addr sin_addr ; // Internet Address 
         
        /*Pad to size of  'struct sockaddr'*/ 
        ...........  
    };
     
    //套接字地址结构体。
    struct sockaddr
    {
        sa_family_t sa_family; // 存放网络层所使用的协议类型(AF_XXX 或 PF_XXX);
        char sa_data[14];   // 里面存放端口号、网络层地址等信息;
    }

       从本质上来说,struct sockaddr与struct sockaddr_in是相同的。

       但在,实际的使用过程中,struct sockaddr_in是 Internet环境下的套接字地址形式,而struct sockaddr是通过的套接字地址个形式。在linux内核中struct sockaddr使用的更多,目的是使linux内核代码更为通用。

       struct sockaddr_in 可以与 struct sockaddr 进行自由的转换。

      

       2.将创建的套接字(struct socket)与套接字地址结构体(struct sockaddr or struct sockaddr_in)进行绑定:

         int kernel_bind(struct socket *sock, struct sockaddr *addr,

                         int addrlen)


         EXPROT_SYMBOL(kernel_bind);

         sock : 为通过sock_create()或sock_create_kern()创建的套接字;

         addr : 为套接字地址结构体;

         addrlen:为套接字地址结构体的大小;

       3.将一个套接字(struct socket)设置为监听状态:

         int kernel_listen(struct socket *sock, int backlog);

         backlog :一般情况下设置为0;

         EXPORT_SYMBOL(kernel_listen);

       4.当把一个套接字设置为监听状态以后,使用这个套接字去监听其它的套接字;

       int kernel_accept(struct socket *sock, struct socket **new_sock,

                          int flags);

       EXPORT_SYMBOL(kernel_accept);

       sock : listening socket 处于监听状态的套接字;

       new_sock : 被监听的套接字;

       flags: struct socket中的flags字段的取值;

     

       5.把一个套接字连接到另一个套接字地址结构体上:

       int kernel_connect(struc socket *sock, struct sockaddr *addr,

                           int addrlen, int flags);

       EXPORT_SYMBOL(kernel_connect);

       sock : struct socket;

       addr : 为另一个新的套接字地址结构体;

       addrlen : 套接字地址结构体的大小;

       flags :file-related flags associated with socket

       6.把一个应用层中的数据发送给另一个设备中的进程:

         int kernel_sendmsg(struct socket *sock, struct msghdr *msg,

                             struct kvec *vec, size_t num, size_t size)

         EXPORT_SYMBOL(kernel_sendmsg);

         sock : 为当前进程中的struct socket套接字;

         msg  : 用于接收来自应用层的数据包;

         kvec : 中存放将要发送出去的数据;

         num  : 见代码;

         size : 为将要发送的数据的长度;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    struct iovec
    {
        void __user *iov_base;
        __kernel_size_t iov_len;
    }
     
    struct msghdr
    {
         //用于存放目的进程所使用的套接字地址
         void *msg_name;  // 用于存放目的进程的struct sockaddr_in 
         int   msg_namelen; // 目的进程的sizeof(struct sockaddr_in)
          
          
         //用于来自应用层的数据
         struct iovec *msg_iov ;// 指向一个struct iovec的数组,数组中的每个成员表示一个数据块
         __kernel_size_t  msg_iovlen ; //数据块数,即struct iovec数组的大小
          
          
         //用于存放一些控制信息
         void *msg_control ;
         __kernel_size_t msg_controllen; //控制信息的长度;
          
          
         //
         int msg_flags;     
          
    }
    1
    2
    3
    4
    5
    struct kvec
    {
        void *iov_base; //用于存放来自应用层的数据;
        size_t iov_len; //来自应用层的数据的长度;
    }

     struct msghdr中的flags字段的取值为:

    wKiom1R9K3zSLJWrAASNJz0QxPc672.jpg

     

    int kernel_sendmsg(struct socket *sock, struct msghdr *msg,

               struct kvec *vec, size_t num, size_t size)函数的实现为:

    wKioL1R8mqCDxdedAALtXFBqeDk456.jpg

     

       有kernel_sendmsg()的实现代码可知,struct kvec中的数据部分最终还是要放到struct msghdr之中去的。

       kernel_sendmsg()的用法:

    wKiom1R9KNyQKTtUAAT07fVKu2A037.jpg

       也可以使用下面这个函数来实现相同的功能:

      int sock_sendmsg(struct socket *sock, struct msghdr *msg,

                        size_t size);

      EXPORT_SYMBOL(sock_sendmsg);

      

      

      7.接受来自另一个网络进程中的数据:

        int kernel_recvmsg(struct socket *sock, struct msghdr *msg,

                  struct kvec *vec, size_t num, size_t size, int flags)

        EXPORT_SYMBOL(kernel_recvmsg);

        sock : 为接受进程的套接字;

        msg  : 用于存放接受到的数据;

        vec  : 用于指向本地进程中的缓存区;

        num  : 为数据块的块数;

        size : 缓存区的大小;

        flags: struct msghdr中的flags字段中的取值范围;

      int kernel_recvmsg()的实现:

    wKiom1R9KnbxNq6eAAKakSYH0Js684.jpg

      kernel_recvmsg()的用法:

    wKioL1R9JlixQrpdAAJD7SWJH2g182.jpg

     

     

      8.关闭一个套接字:

        void sock_release(struct socket *sock);

          用于关闭一个套接字,并且如果一个它struct socket绑定到了一个struct

    inode节点上的话,相应的struct inode也会被释放。

     

      

        以上这些函数位于linux源代码包中的/net/socket.c之中。

    本文出自 “阿辉仔” 博客,请务必保留此出处http://weiguozhihui.blog.51cto.com/3060615/1585297

  • 相关阅读:
    Dvwa——环境部署
    DVWA--简介
    华为OSPF与ACL综合应用
    ensp综合题二
    ensp综合题一
    OSPF
    静态路由
    生成树
    Vlan
    2020.07.28【省选B组】模拟 总结
  • 原文地址:https://www.cnblogs.com/feng9exe/p/6994637.html
Copyright © 2011-2022 走看看