- 我最近开了几个专栏,诚信互三!
====> |||《算法专栏》::刷题教程来自网站《代码随想录》。|||
====> |||《C++专栏》::记录我学习C++的经历,看完你一定会有收获。|||
====> |||《Linux专栏》::记录我学习Linux的经历,看完你一定会有收获。|||
====> |||《C#专栏》::记录我复习C#的经历,深度理解,查漏补缺,不定期更新。|||
====> |||《计算机网络专栏》::记录我学习计算机网络,看完你一定会有收获。|||
详细解析socket
- Listen参数backlog
- 文件描述符和socket的关系
Listen参数backlog
根据TCP三次握手建立链接可知。
accept接口不参与三次链接的建立过程。
backlog + 1表示TCP的全链接队列中,最多可以缓存多少个已经完成三次握手,而因为各种原因,未被accept处理的链接。
当全链接队列中的链接个数已经是backlog+1后,其他客户端在申请建立链接,则客户端会卡在SYN_SENT状态中,其代表该链接并未建立。
关于backlog参数大小的设置也很讲究。
首先我们不能设置的太小,如果设置的太小,则当上层很忙时,那么只能有少量的链接被挂在全链接队列中,其他客户端不论怎么申请,都无法成功建立链接,这时客户可能就不会访问该服务器了,当服务器闲下来后,全链接队列中的链接过少,在上层处理完后,服务器就进入了闲置状态,增加了服务器的闲置率。
其次我们不能设置的太大,如果太大,则当上层很忙时,大量的链接都被挂在全链接队列中,这会导致内存开销很大,同时,大量链接挂接在全链接队列中,靠后的链接可能需要很久才能被处理,导致用户体验不好。
文件描述符和socket的关系
在进程部分,我们详细的讲解了pcb的内容,pcb中存在文件描述符数组的指针,指向一个文件描述符数组,每个数组中存放一个file类型的结构体,我们知道socket接口返回值也是一个文件描述符,这期间是存在关系的。
如上图,这是创建套接字的公共部分,我们不论创建TCPsocket/listensock还是UDPsocket还是本地的socket,还是网络socket,都会存在这样的结构,而其中,struct socket结构体被成为通用socket。
通用socket中存在一些字段,可以确定其是本地还是网络,TCP还是UDP。
比如type字段,可以通过是面向数据报还是面向字节流来判断是TCP还是UDP。
socket中存在一个字段struct sock类型的指针,可以指向tcp_sock或者udp_sock,我们之前说过,TCP是面向链接的,所以在tcp_sock中存在结构体struct inet_connect_sock,其中就维护了全链接队列。 同时在inet_sock中,存储了socket的四元组{src ip, dest ip, src port, dest port}
TCP全链接队列的字段,存在struct requst_sock* head和tail指针,指向struct request_sock结构,该结构内存在一个struct sock结构的指针sk,sk指向一个tcp_sock,这样就将所有的tcp_sock链接在全链接队列中了。
当我们accept后,就取出队头链接并且创建公共结构
让公共结构中的struct sock* sk指针指向队头链接,就代表accept成功了。
在struct sock结构体中,存储了两个指针,sk_recive_buffer,sk_send_buffer,这两个指针指向struct sk_buff结构体,该结构体内,维护了三个指针head,tail和data,head指向一个报文的报头,tail指向一个报文的结尾,data指向报文的有效载荷,这代表了一个报文,当许多个sk_buff结构体相连,形成了TCP接收/发送缓冲区
我们还需要知道在三次握手中,我们的链接在什么时候加入全链接队列,如下图。
在第一次握手时,不会创建tcp_sock,而是创建request_sock将其放入半连接队列中,这是为了防止,后续如果建立链接失败,则开辟的更大的tcp_sock的空间就浪费了,同时,在request_sock结构中,会存储发送端发送的mss信息,同时在下一次握手时,将自己的mss信息发出,随后双方都有了双方的mss信息,取最小的作为通信时的mss,最大程度防止分片。
当服务器收到第三次握手请求时,代表TCP链接已经建立完成,此时在创建tcp_sock结构,同时将半链接队列中的request_sock中的sk指针指向tcp_sock,同时将request_sock连入全链接队列中。 等待上层的accept。