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