个人主页:Lei宝啊
愿所有美好如期而遇
本节我们将解释socket创建套接字的原理,我们现在能够理解到的是,调用socket系统调用,会创建好一个套接字并返回一个文件描述符。
现在,我们先来看这样一张图:
(我们以listen套接字为例)当我们调用系统调用创建一个套接字时,操作系统会在内核中为我们创建一个struct socket结构,我们现在可以将他理解成网络socket的入口,同时,struct file中有这样一个字段:void* private_data,他指向这个套接字,这个套接字中也有这样一个字段:struct file *file 回指向这个struct file,所以我们也就可以通过文件描述符找到struct file,再通过private_data找到socket。
我们看看socket的结构:
但是事实上,我们创建tcp连接时,内核创建的结构体叫做tcp_sock:
我们简单介绍几个字段:
但是这些我们不管,我们关心的是这个字段:
struct inet_connection_sock,网络连接套接字:
icsk_timeout超时时间,icsk_retransmit_timer重传时间,我们关心的字段是struct request_sock_queue,他是什么呢?就是全连接队列:
当创建好tcp_sock时,他就会被挂在这个队列下,等待accept来获取。同时,我们仍然需要注意到这样一个字段:struct inet_sock:
我们可以看到daddr目的ip地址,saddr源ip地址,dport目的端口号,sport源端口号,这里面我们仍然可以看到一个字段:struct sock,同时,我们就发现,我们这里关注的字段都是处在最开始的位置。
struct sock里有这样两个字段:
我们常说tcp中有写缓冲区和读缓冲区,他们通过某种方式可以转换成上面的结构, sk_buff_head:
通过链表对sk_buff结构进行管理(每一个sk_buff管理着一个报文),sk_buff:
里面的tail和head管理着一段内存空间,这段内存空间就存放着协议栈的报文,通过这两个指针的移动对报文进行管理。
所以整体结构是这样的:
这样,我们通过sk指针就可以访问到这些结构体,如果我们想访问tcp_sock那么就强转成tcp_sock*,如果我们想访问inet_sock中的源端口目的端口就,就强转成inet_sock*。
对于udp来说,没有inet_connection_sock这个结构,socket辨别Udp和Tcp的方式就是我们刚开始说的socket中的type字段。
现在,我们来说普通套接字,当服务器完成三次握手后,OS就会创建好tcp_sock,然后将他连入到全连接队列中,当accept时,OS会首先创建struct file,接着是socket,然后通过socket中的sk指针和tcp_sock关联起来,将struct file的地址填入fd_arrry中,返回文件描述符,这样,普通套接字就创建好了,需要注意的是,普通套接字不关心全连接队列。