12.3.3 套接字的工作原理

 

INET 套接字就是支持 Internet 地址族的套接字,它位于TCP协议之上, BSD套接字之下,如图12.8,这里也体现了Linux网络模块分层的设计思想。

                      

 

 

 

 

 

 

 

 

 12.8 INET 套接字

INET BSD 套接字之间的接口通过 Internet 地址族套接字操作集实现,这些操作集实际是一组协议的操作例程,在include/linux/net.h中定义为proto_ops

struct proto_ops {

   int   family;

 

   int   (*release)      (struct socket *sock);

   int   (*bind)         (struct socket *sock, struct sockaddr *umyaddr,

                          int sockaddr_len);

   int   (*connect)      (struct socket *sock, struct sockaddr *uservaddr,

                         int sockaddr_len, int flags);

   int   (*socketpair)   (struct socket *sock1, struct socket *sock2);

   int   (*accept)       (struct socket *sock, struct socket *newsock,

                          int flags);

   int   (*getname)      (struct socket *sock, struct sockaddr *uaddr,

                          int *usockaddr_len, int peer);

unsigned int (*poll)  (struct file *file, struct socket *sock, struct poll_table_struct *wait);

   int   (*ioctl)        (struct socket *sock, unsigned int cmd,

                         unsigned long arg);

  int   (*listen)       (struct socket *sock, int len);

  int   (*shutdown)     (struct socket *sock, int flags);

  int   (*setsockopt)   (struct socket *sock, int level, int optname,

                          char *optval, int optlen);

  int   (*getsockopt)   (struct socket *sock, int level, int optname,

                          char *optval, int *optlen);

int   (*sendmsg)      (struct socket *sock, struct msghdr *m, int  total_len, struct scm_cookie *scm);

int   (*recvmsg)      (struct socket *sock, struct msghdr *m, int total_len, int flags, struct scm_cookie *scm);

int   (*mmap)         (struct file *file, struct socket *sock, struct vm_area_struct * vma);

ssize_t (*sendpage)   (struct socket *sock, struct page *page, int offset, size_t size, int flags);

};

这个操作集类似于文件系统中的file_operations结构。BSD 套接字层通过调用 proto_ops  结构中的相应函数执行任务。BSD 套接字层向 INET 套接字层传递 socket 数据结构来代表一个 BSD 套接字,socket结构在include/linux/net.h中定义如下:

struct socket

{

       socket_state            state;  

 

       unsigned long           flags;

      struct proto_ops        *ops;

      struct inode            *inode;

      struct fasync_struct    *fasync_list; /* Asynchronous wake up list    */

      struct file             *file;       /* File back pointer for gc     */

      struct sock             *sk;

      wait_queue_head_t       wait;

 

      short                   type;

      unsigned char           passcred;

  };

但在 INET 套接字层中,它利用自己的 sock 数据结构来代表该套接字,因此,这两个结构之间存在着链接关系,Sock结构定义于include/net/sock.h(此结构有80多行,在此不予列出)。在 BSD socket 数据结构中存在一个指向sock的指针sk,而在sock中又有一个指向socket的值中,这两个指针将 BSD socket 数据结构和sock 数据结构链接了起来。通过这种链接关系,套接字调用就可以方便地检索到 sock 数据结构。实际上,sock 数据结构可适用于不同的地址族,它也定义有自己的协议操作集proto。在建立套接字时,sock 数据结构的协议操作集指针指向所请求的协议操作集。如果请求 TCP 协议,则 sock 数据结构的协议操作集指针将指向 TCP 的协议操作集。

 进程在利用套接字进行通讯时,采用客户-服务器模型。服务器首先创建一个套接字,并将某个名称绑定到该套接字上,套接字的名称依赖于套接字的底层地址族,但通常是服务器的本地地址。套接字的名称或地址通过 sockaddr 数据结构指定,该结构定义于include/linux/socket.h中:

 

struct sockaddr {

        sa_family_t     sa_family;      /* address family, AF_xxx       */

        char            sa_data[14];    /* 14 bytes of protocol address */

};

    对于 INET 套接字来说,服务器的地址由两部分组成,一个是服务器的 IP 地址,另一个是服务器的端口地址。已注册的标准端口可查看 /etc/services 文件。将地址绑定到套接字之后,服务器就可以监听请求链接该绑定地址的传入连接。连接请求由客户生成,它首先建立一个套接字,并指定服务器的目标地址以请求建立连接。传入的连接请求通过不同的协议层最终到达服务器的监听套接字。服务器接收到传入的请求后,如果能够接受该请求,服务器必须创建一个新的套接字来接受该请求并建立通讯连接(用于监听的套接字不能用来建立通讯连接),这时,服务器和客户就可以利用建立好的通讯连接传输数据。

BSD 套接字上的详细操作与具体的底层地址族有关,底层地址族的不同实际意味着寻址方式、采用的协议等的不同。Linux 利用 BSD 套接字层抽象了不同的套接字接口。在内核的初始化阶段,内建于内核的不同地址族分别以 BSD 套接字接口在内核中注册。然后,随着应用程序创建并使用 BSD 套接字,

内核负责在 BSD 套接字和底层的地址族之间建立联系。这种联系通过交叉链接数据结构以及地址族专有的支持例程表建立。

在内核中,地址族和协议信息保存在inet_protos 向量中,其定义于include/net/protocol.h

     struct inet_protocol *inet_protos[MAX_INET_PROTOS];

 

     /* This is used to register protocols. */

struct inet_protocol

{

         int                     (*handler)(struct sk_buff *skb);

         void                    (*err_handler)(struct sk_buff *skb, u32 info);

        struct inet_protocol    *next;

         unsigned char           protocol;

         unsigned char           copy:1;

        void                    *data;

        const char              *name;

};

   每个地址族由其名称以及相应的初始化例程地址代表。在引导阶段初始化套接字接口时,内核调用每个地址族的初始化例程,这时,每个地址族注册自己的协议操作集。协议操作集实际是一个例程集合,其中每个例程执行一个特定的操作。