套接字数据结构
linux系统下,每个套接字都有一个struce socket和struc sock的数据结构实例。
struct socket
1 2 3 4 5 6 7 8 9 10 11
   | struct socket { 	socket_state		state; 	unsigned long		flags; 	struct proto_ops	*ops; 	struct fasync_struct	*fasync_list; 	struct file	*file; 	struct sock	*sk; 	wait_queue_head_t	wait; 	short type; 	unsigned char passcred; };
  | 
 
struct socke与socket描述符一一对应,每个套接字都对应内核中唯一的struct socket,struct sock是struce socket中的一个字段。
struct sock
sock结构体包含了套接字的全部属性,其中的一个sock_common是套接字的共有属性,所有协议族的这些属性都是一样的。
sock_common是sock里的一个成员,最重要的成员就在sock_common里。
1 2 3 4 5 6 7 8 9 10 11 12 13
   | struct sock { 	struct sock_common	__sk_common;     ... } struct sock_common { 	unsigned short	skc_family; 	volatile unsigned char	skc_state; 	unsigned char skc_reuse; 	int	skc_bound_dev_if; 	struct hlist_node	skc_node; 	struct hlist_node	skc_bind_node; 	atomic_t skc_refcnt; };
  | 
 
从hlist_node成员可以看出,sock组织在特定协议的哈希链表中,skc_node是哈希节点。
socket在内核中的组织

套接字接口
socket
socket()创建套接字,产生系统调用中断,调用内核套接字函数sys_socketcall,在将调用传送到sys_socket函数,
sock_create函数完成通用的套接字创建初始化工作,其中就创建了新的struct socket。
sock_alloc分配了struct socket需要的预留内存空间,也会分配struct inode实例的内存空间。
然后调用特定的协议族创建函数。
sock_map_fd返回了分配的文件描述符。
bind
sys_bind将套接字和地址绑定起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen) { 	struct socket *sock; 	char address[MAX_SOCK_ADDR]; 	int err;
  	if((sock = sockfd_lookup(fd,&err))!=NULL) 	{ 		if((err=move_addr_to_kernel(umyaddr,addrlen,address))>=0) { 			err = security_socket_bind(sock, (struct sockaddr *)address, addrlen); 			if (err) { 				sockfd_put(sock); 				return err; 			} 			 			err = sock->ops->bind(sock, (struct sockaddr *)address, addrlen); 		} 		 		sockfd_put(sock); 	}			 	return err; }
   | 
 
首先根据文件描述符查找到socket实例,绑定之前,先将用户空间的地址拷贝到内核空间,然后检查传入地址是否正确。
bind函数完成绑定操作。
connect
主动连接
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
   | asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen) { 	struct socket *sock; 	char address[MAX_SOCK_ADDR]; 	int err;
  	sock = sockfd_lookup(fd, &err); 	if (!sock) 		goto out; 	 	err = move_addr_to_kernel(uservaddr, addrlen, address); 	if (err < 0) 		goto out_put;
  	 	err = security_socket_connect(sock, (struct sockaddr *)address, addrlen); 	if (err) 		goto out_put;
  	 	err = sock->ops->connect(sock, (struct sockaddr *) address, addrlen, 				 sock->file->f_flags); out_put: 	sockfd_put(sock); out: 	return err; }
 
   | 
 
listen
listen 的主要工作就是申请和初始化接收队列,包括全连接队列和半连接队列。
调用listen后,内核会建立两个队列
- SYN队列,接受到请求,但是没完成三次握手
 
- ACCEPT队列,已经玩长城了三册握手的队列
 
accept
1
   | int accept(int listen_sockfd, struct sockaddr *addr, socklen_t *addrlen)
   | 
 
从ACCEPT队列中拿一个连接,并生成一个新的描述符,跟原来的listen_sockfd相比,新fd的请求端IP地址,请求端口被初始化了。
参考资料
https://mp.weixin.qq.com/s?__biz=MjM5Njg5NDgwNA==&mid=2247485737&idx=1&sn=baba45ad4fb98afe543bdfb06a5720b8&scene=21#wechat_redirect
linux2.6.11
https://www.cnblogs.com/zengzy/p/5107516.html