套接字数据结构
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