引言

东哥和欢神确实让我们注意到了很多平时没有注意到的东西,下面博客参考杨博东学长的思路
​杨博东的博客

问题

  1. listen 中的 backlog参数到底是什么意思
  2. 若服务器全连接队列满而半连接队列接收到ack即将转入全连接队列该怎么办.

结论

  1. backlog参数的意思是全连接队列的长度加1
  2. 服务器维护一个会在一段时间后断掉, 而客户端仍维护一个状态为ESTABLISHED的连接

验证

验证思路就是客户端一次发起十次连接,然后服务器设置backlog为5(观察连接双方状态), 一次accept就会使得全连接队列减一,而半连接队列中的一个条目转入全连接队列(观察全连接队列满该如何操作)

东哥的代码足够简洁了,直接放在这里

server

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<strings.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

#define
#define//定义创建的线程数量

struct sockaddr_in serv_addr;

void *func()
{
int conn_fd;
conn_fd = socket(AF_INET,SOCK_STREAM,0);
printf("conn_fd : %d\n",conn_fd);

if( connect(conn_fd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr_in)) == -1) {
printf("connect error\n");
}

while(1) {}
}

int main(int argc,char *argv[])
{
memset(&serv_addr,0,sizeof(struct sockaddr_in));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
inet_aton("127.0.0.1",(struct in_addr *)&serv_addr.sin_addr);
int retval;

//创建线程并且等待线程完成
pthread_t pid[thread_num];
for(int i = 0 ; i < thread_num; ++i)
{
pthread_create(&pid[i],NULL,&func,NULL);

}

for(int i = 0 ; i < thread_num; ++i)
{
pthread_join(pid[i],(void*)&retval);
}

return 0;
}

client

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/time.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>

#define//端口号
#define//BACKLOG大小

void my_err(const char* msg,int line)
{
fprintf(stderr,"line:%d",line);
perror(msg);
}


int main(int argc,char *argv[])
{
int conn_len;
int sock_fd,conn_fd;
struct sockaddr_in serv_addr,conn_addr;


if((sock_fd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
my_err("socket",__LINE__);
exit(1);
}

memset(&serv_addr,0,sizeof(struct sockaddr_in));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);


if(bind(sock_fd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr_in)) == -1) {
my_err("bind",__LINE__);
exit(1);
}

if(listen(sock_fd,BACKLOG) == -1) {
my_err("sock",__LINE__);
exit(1);
}

conn_len = sizeof(struct sockaddr_in);


sleep(10);
printf("one accept\n");
accept(sock_fd,(struct sockaddr *)&conn_addr,(socklen_t *)&conn_len);

sleep(10);
printf("one accept\n");
accept(sock_fd,(struct sockaddr *)&conn_addr,(socklen_t *)&conn_len);

sleep(10);
printf("one accept\n");
accept(sock_fd,(struct sockaddr *)&conn_addr,(socklen_t *)&conn_len);


while(1) {}
return 0;
}

终端执行

watch -n 1 "netstat -natp | grep 8888"
watch的意思是重复执行一个指令,并全屏显示,默认为2秒执行一次, -n参数为指定几秒刷新一次
netstat可以按照各个协议分别显示其统计数
-a (all)显示所有选项,默认不显示LISTEN相关
-t (tcp)仅显示tcp相关选项
-n 拒绝显示别名,能显示数字的全部转化成数字。
-p 显示建立相关链接的程序名

然后两个终端分别执行程序 我们可以看到刚开始是这样的

listen backlog的参数_全连接


一共十六个信息 这是合理的 客户端四个SYN_SEN状态,即六个全连接状态,四个半连接状态,服务器有六个全连接状态, 四个还未接收到SYN

listen backlog的参数_全连接_02

接收一个连接后服务器全连接队列减少一个,半连接队列补上一个,现在服务器有三个SYN_RECV状态,客户端全部ESTABLISHED状态,共二十条信息

listen backlog的参数_#include_03


SYN_RECV减少 ESTABLISTEN增加 共二十条信息

listen backlog的参数_全连接_04


SYN_RECV减少 ESTABLISTEN增加 共二十条信息

listen backlog的参数_#include_05


一段时间后SYN_RECV自动丢失,共十九条信息,客户端还是维持连接,为ESTABISHED状态

那么这个队列的长度究竟有什么用呢

下面是找到的比较好的英文解释 在这里翻译一下(引用第三条)

The duration that affects the rate at which new connections are accepted is the time spent on the queue of pending incoming connections. This duration is equal to the round trip time for the SYN|ACK message and its ACK response plus the time taken for the client to process the SYN|ACK message plus the delay for the server to process the ACK and call accept().

影响接收新连接的速率的时间是花费在挂起于传入连接队列上的时间,这个时间等于SYN/ACK的RTT及ACK响应的时间, 以及客户端处理SYN/ACK的时间加上服务器处理ACK的时间,还有调用accept()的时间.

The rate at which new connections can be accepted is equal to the
number of entries which can fit on the listen queue divided by the
average length of time each entry spends on the queue. Therefore, the
larger the queue, the greater the rate at which new connection
requests can be accepted.

新连接可以被接受的速率等于可以容纳在监听队列中的连接数量除以每一个连接的花费在队列上的时间,所以,越大的队列长度就意味着越快的接收连接的速度.

我觉的也其实很好想,因为上面RTT以及处理这些包括accept的花费的时间是必须的,意味着挂起的时间越长,消耗的时间越长,

总结

backlog参数的意思我们可以确定为全连接队列的长度
但是还有一个问题没有解决, 就是若全连接队列已满,半连接队列中的条目在接收到ACK的时候该什么办,在TCP/IP详解中是这样说的

如果队列中没有足够的空间分配给新的连接,TCP将会延迟对SYN做出响应,从而给应用程序一个跟上节奏的机会,Linux在这一方面有着独特的行为,它坚持在能力允许的的范围内不忽略进入的连接,如果系统控制变量net.ipv4.tcp_abort_on_已被设定,新进入的连接会被重置报文段重新置位.

这个系统变量的路径是这样的

/proc/sys/net/ipv4/tcp_abort_on_overflow

我的系统配置是

Deepin 15.7

显示结果如下

listen backlog的参数_半连接_06

所以client一直没有断开 始终保持ESTABISHED状态,问题到了这里就结束了,其实还应该抓抓包的,有时间再去补充.

作者水平有限,不足之处请指出.

引用:

​https://sysctl-explorer.net/net/ipv4/tcp_abort_on_overflow/​

​​​https://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/023/2333/2333s2.html​​ TCP/IP详解

​就是要你懂TCP–半连接队列和全连接队列​​f

tcp的半连接与完全连接队列