曾经有人问我套接字编程中listen的第二个参数backlog是什么意思?多大的值合适?我不假思索地回答它表示服务器可以接受的并发请求的最大值。然而事实真的是这样的吗?

TCP通过三次握手建立连接的过程应该都不陌生了。从服务器的角度看,它分为以下几步

将TCP状态设置为LISTEN状态,开启监听客户端的连接请求

收到客户端发送的SYN报文后,TCP状态切换为SYN RECEIVED,并发送SYN ACK报文

收到客户端发送的ACK报文后,TCP三次握手完成,状态切换为ESTABLISHED

在Unix系统中,开启监听是通过listen完成。

int listen(int sockfd, int backlog)

listen有两个参数,第一个参数sockfd表示要设置的套接字,本文主要关注的是其第二个参数backlog;

将其描述为已完成的连接队列(ESTABLISHED)与未完成连接队列(SYN_RCVD)之和的上限。

一般我们将ESTABLISHED状态的连接称为全连接,而将SYN_RCVD状态的连接称为半连接

当服务器收到一个SYN后,它创建一个子连接加入到SYN_RCVD队列。在收到ACK后,它将这个子连接移动到ESTABLISHED队列。最后当用户调用accept()时,会将连接从ESTABLISHED队列取出。

是 Posix 不是 TCP

listen只是posix标准,不是TCP的标准!不是TCP标准就意味着不同的内核可以有自己独立的实现

The backlog argument provides a hint to the implementation which the implementation shall use to limit the number of outstanding connections in the socket's listen queue.

Linux是什么行为呢 ? 查看listen的man page

The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection requests.

什么意思呢?就是说的在Linux 2.2以后, backlog只限制完成了三次握手,处于ESTABLISHED状态等待accept的子连接的数目了。

真的是这样吗?于是我决定抄一个小程序验证一下:

服务器监听50001端口,并且设置backlog = 4。注意,我为了将队列塞满,没有调用accept。

#include

#include

#include

#include

#include

#define BACKLOG 4

int main(int argc, char **argv)

{

int listenfd;

int connfd;

struct sockaddr_in servaddr;

listenfd = socket(PF_INET, SOCK_STREAM, 0);

bzero(&servaddr, sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

servaddr.sin_port = htons(50001);

bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

listen(listenfd, BACKLOG);

while(1)

{

sleep(1);

}

return 0;

}

客户端的代码

#include

#include

#include

#include

#include

#include

int main(int argc, char **argv)

{

int sockfd;

struct sockaddr_in servaddr;

sockfd = socket(PF_INET, SOCK_STREAM, 0);

bzero(&servaddr, sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_port = htons(50001);

servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

if (0 != connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)))

{

printf("connect failed!\n");

}

else

{

printf("connect succeed!\n");

}

sleep(30);

return 1;

}

为了排除syncookie的干扰,我首先关闭了syncookie功能

echo 0 > /proc/sys/net/ipv4/tcp_syncookies

由于我设置的backlog = 4并且服务器始终不会accept。因此预期会建立 4 个全连接, 但实际却是

root@ubuntu-1:/home/user1/workspace/client# ./client &

[1] 12798

root@ubuntu-1:/home/user1/workspace/client# connect succeed!

./client &

[2] 12799

root@ubuntu-1:/home/user1/workspace/client# connect succeed!

./client &

[3] 12800

root@ubuntu-1:/home/user1/workspace/client# connect succeed!

./client &

[4] 12801

root@ubuntu-1:/home/user1/workspace/client# connect succeed!

./client &

[5] 12802

root@ubuntu-1:/home/user1/workspace/client# connect succeed!

./client &

[6] 12803

root@ubuntu-1:/home/user1/workspace/client# connect succeed!

./client &

[7] 12804

root@ubuntu-1:/home/user1/workspace/client# connect succeed!

./client &

[8] 12805

root@ubuntu-1:/home/user1/workspace/client# connect succeed!

./client &

[9] 12806

root@ubuntu-1:/home/user1/workspace/client# connect succeed!

./client &

[10] 12807

root@ubuntu-1:/home/user1/workspace/client# connect failed!

看!客户器竟然显示成功建立了 9 次连接!

用netstat看看TCP连接状态

> netstat -t

Active Internet connections (w/o servers)

Proto Recv-Q Send-Q Local Address Foreign Address State

tcp 0 0 localhost:50001 localhost:55792 ESTABLISHED

tcp 0 0 localhost:55792 localhost:50001 ESTABLISHED

tcp 0 0 localhost:55798 localhost:50001 ESTABLISHED

tcp 0 1 localhost:55806 localhost:50001 SYN_SENT

tcp 0 0 localhost:50001 localhost:55784 ESTABLISHED

tcp 0 0 localhost:50001 localhost:55794 SYN_RECV

tcp 0 0 localhost:55786 localhost:50001 ESTABLISHED

tcp 0 0 localhost:55800 localhost:50001 ESTABLISHED

tcp 0 0 localhost:50001 localhost:55786 ESTABLISHED

tcp 0 0 localhost:50001 localhost:55800 SYN_RECV

tcp 0 0 localhost:55784 localhost:50001 ESTABLISHED

tcp 0 0 localhost:50001 localhost:55796 SYN_RECV

tcp 0 0 localhost:50001 localhost:55788 ESTABLISHED

tcp 0 0 localhost:55794 localhost:50001 ESTABLISHED

tcp 0 0 localhost:55788 localhost:50001 ESTABLISHED

tcp 0 0 localhost:50001 localhost:55790 ESTABLISHED

tcp 0 0 localhost:50001 localhost:55798 SYN_RECV

tcp 0 0 localhost:55790 localhost:50001 ESTABLISHED

tcp 0 0 localhost:55796 localhost:50001 ESTABLISHED

整理一下就是下面这样

从上面可以看出,一共有5条连接对是ESTABLISHEDESTABLISHED连接, 但还有4条连接对是SYN_RECVESTABLISHED连接, 这表示对客户端三次握手已经完成了,但对服务器还没有! 回顾一下TCP三次握手的过程,造成这种连接对原因只有可能是服务器将客户端最后发送的握手ACK被丢弃了!

还有一个问题,我明明设置的backlog的值是 4,可为什么还能建立5个连接 ?!

去内核找原因

我实验用的机器内核是4.4.0

前面提到过已完成连接队列和未完成连接队列这两个概念, Linux有这两个队列吗 ? Linux 既有又没有! 说有是因为内核中可以得到两种连接各自的长度; 说没有是因为 Linux只有已完成连接队列实际存在, 而未完成连接队列只有长度的记录!

每一个LISTEN状态的套接字都有一个struct inet_connection_sock结构, 其中的accept_queue从名字上也可以看出就是已完成三次握手的子连接队列.只是这个结构里还记录了半连接请求的长度!

struct inet_connection_sock {

// code omitted

struct request_sock_queue icsk_accept_queue;

// code omitted

}

struct request_sock_queue {

// code omitted

atomic_t qlen; // 半连接的长度

atomic_t young; // 一般情况, 这个值 = qlen

struct request_sock *rskq_accept_head; // 已完成连接的队列头

struct request_sock *rskq_accept_tail; // 已完成连接的队列尾

// code omitted

};

需要注意的是这个 young, 一般情况下,它和 qlen 是共同变化的, 但有时不会,代码中的注释对这点有说明,即 1s 定时器超时都没有收到对端的 ACK。这个半连接就"成熟"了。

Normally all the openreqs are young and become mature(i.e. converted to established socket) for first timeout.If synack was not acknowledged for 1 second, it means one of the following things: synack was lost, ack was lost, rtt is high or nobody planned to ack (i.e. synflood).

所以一般情况下连接建立时,服务端的变化过程是这样的:

收到SYN报文, qlen++,young++

收到ACK报文, 三次握手完成,将连接加入accept队列,qlen--,young--

用户使用accept,将连接从accept取出.

再来看内核收到SYN握手报文时的处理, 由于我关闭了syncookie,所以一旦满足了下面代码中的两个条件之一就会丢弃报文

int tcp_conn_request(struct request_sock_ops *rsk_ops,

const struct tcp_request_sock_ops *af_ops,

struct sock *sk, struct sk_buff *skb)

if ((net->ipv4.sysctl_tcp_syncookies == 2 ||

inet_csk_reqsk_queue_is_full(sk)) && !isn) { // 条件1: 半连接 >= backlog

want_cookie = tcp_syn_flood_action(sk, skb, rsk_ops->slab_name);

if (!want_cookie)

goto drop;

}

if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) { // 条件2: 全连接sock > backlog 并且 半连接队列的young字段 > 1

NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);

goto drop;

}

// code omitted

"半连接队列的young字段 > 1" 表示网络很忙,有 SYNACK 丢失了(没有收到对端的第三次握手的 ACK),但在我们的简单例子中,client 的 ACK 总是很及时的,所以这个条件不会满足,也就是说丢弃 SYN 报文的条件 2 只剩下全连接sock > backlog

下面是收到ACK握手报文时的处理

struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb,

struct request_sock *req,

struct dst_entry *dst,

struct request_sock *req_unhash,

bool *own_req)

{

// code omitted

if (sk_acceptq_is_full(sk)) // 全连接 > backlog, 就丢弃

goto exit_overflow;

newsk = tcp_create_openreq_child(sk, req, skb); // 创建子套接字了

// code omitted

}

所以这样就可以解释实验现象了!

前4个连接请求都可以顺利创建子连接, 全连接队列长度 = backlog = 4, 半连接数目 = 0

第5个连接请求, 由于sk_acceptq_is_full的判断条件是>而不是>=,所以依然可以建立全连接

第6-9个连接请求到来时,由于半连接的数目还没有超过backlog,所以还是可以继续回复SYNACK,但收到ACK后已经不能再创建子套接字了,所以TCP状态依然为SYN_RECV.同时半连接的数目也增加到backlog.而对于客户端,它既然能收到SYNACK握手报文,因此它可以将TCP状态变为ESTABLISHED,

第10个请求到来时, 由于半连接的数目已经达到backlog,因此,这个SYN报文会被丢弃.

内核的问题

从以上的现象和分析中,我认为内核存在以下问题

accept队列是否满的判断用>=比>更合适, 这样才能体现backlog的作用

accept队列满了,就应该拒绝半连接了,因为即使半连接握手完成,也无法加入accept队列,否则就会出现SYN_RECV--ESTABLISHED这样状态的连接对!这样的连接是不能进行数据传输的!

问题2在16年的补丁中已经修改了! 所以如果你在更新版本的内核中进行相同的实验, 会发现客户端只能连接成功5次了,当然这也要先关闭syncookie

但问题1还没有修改! 如果以后修改了,我也不会意外

(完)

REF

php backlog,backlog参数对TCP连接建立的影响相关推荐

  1. [计算机网络] - 调节参数提高 TCP 性能

    转载自:https://blog.csdn.net/qq_34827674/article/details/106627403 1. TCP 三次握手的性能提升 TCP 是面向连接的.可靠的.双向传输 ...

  2. 【TCP专题】TCP连接建立

    今天开始,我们整理一些关于TCP协议的知识.这块的内容写起来是非常费劲的,因为本身TCP协议就不是一个简单的协议,它能获得如今的地位,和其复杂且出色的表现是分不开的. 什么是面向连接 众所周知,TCP ...

  3. TCP 连接建立 故障排查

    TCP连接的状态详解以及故障排查 我们通过了解TCP各个状态,可以排除和定位网络或系统故障时大有帮助. 1.TCP状态 了解TCP之前,先了解几个命令: linux查看tcp的状态命令: 1) net ...

  4. TCP连接建立与终止,及状态转换

    TCP连接建立 三路握手 三路握手发生在客户端发起connect请求到服务端accept返回中,在三路握手发生前,服务端 必须准备好接受外来连接,这通常通过服务端调用 (socket.bind.lis ...

  5. 【计算机网络】传输层 : TCP 连接管理 ( TCP 连接建立 | 三次握手 | TCP 连接释放 | 四次挥手 )

    文章目录 一.TCP 连接管理 二.TCP 连接建立 三.TCP 连接建立 相关报文段 字段 四.SYN 洪泛攻击 五.TCP 连接释放 一.TCP 连接管理 TCP 传输数据过程 : 建立连接 -& ...

  6. 网络性能测试中CHARIOT脚本参数配置对测试数据的影响

    写在前面,本篇文档收录于前公司文档库内,具体作者未知.网络也没有相关内容介绍,觉得在网络测试这块chariot是用的最多的性能测试工具,分享这篇文档.如有侵权请告知删除,谢谢. 现行的网卡测试中,CH ...

  7. 【RFC3449 网络路径不对称对 TCP 性能的影响】(翻译)

    原文 https://datatracker.ietf.org/doc/html/rfc3449 概述 本文档描述了由于非对称效应而产生的 TCP 性能问题.由于不同的根本原因,这些问题出现在几个接入 ...

  8. 简历写了会Kafka,面试官90%会让你讲讲acks参数对消息持久化的影响

    面试大厂时,一旦简历上写了Kafka,几乎必然会被问到一个问题:说说acks参数对消息持久化的影响? 这个acks参数在kafka的使用中,是非常核心以及关键的一个参数,决定了很多东西. 所以无论是为 ...

  9. 虚拟同步电机(VSG)调频动态特性和控制参数对储能配置的影响

    根据之前求得的兼具惯量支撑和一次调频的VSG传函与仅具有惯量支撑功能的VSG传函对比,下面贴公式: 仅具惯量支撑: 兼具一次调频和惯量支撑: 可以看到极点是没有区别的,但是零点不一样,后者把原来在原点 ...

最新文章

  1. 把计算机网络关闭啦怎么打开,我在笔记本电脑里的“打开或关闭系统图标”中关闭了“网络系统图标”,哪么怎样做才能打开...
  2. 你应该知道的缓存进化史
  3. CSS background 属性
  4. Python:检查‘Dictionary‘是否为空似乎不起作用
  5. 编写QT代码实现与FlightGear通信
  6. 语音信号处理 | 傅里叶变换、短时傅里叶变换、小波变换、希尔伯特变换、希尔伯特黄变换
  7. 机器学习面试要点总结
  8. oracle两个date相减_oracle获取年月日,两个日期相减
  9. delphi 各新版本特性收集
  10. 【Gym - 101350M Make Cents?】 STL - map
  11. Manjaro安装教程
  12. HJ68 成绩排序 ●●
  13. project设置工期为1个工作日,但开始时间与结束时间不是同一天,如何解决或者是设置?
  14. org.postgresql.util.psqlexception总结
  15. 燕尾服 CodeForces - 573A
  16. 前端面试题,前端组件化、工程化、模块化的概念
  17. 密码应用体系建设(政务方向)
  18. Android Studio 常见问题 与 操作指南
  19. 小程序前后台切换运行机制
  20. 小程序开发之微信接入微信调用wenxin4j

热门文章

  1. 纸上谈兵_FPGA配置_多重加载
  2. IT宅男利用Python网络爬虫获取Mikan动漫资源(属于宅男的快乐)
  3. 人工装卸车设计、吊车起重机设计、电子驻车制动系统(EPB)、汽车电动助力转向系统、车窗举升机构、环保垃圾压缩车设计、汽车前悬挂和转向系统设计、汽车离合器外壳工艺工装设计……
  4. 国密算法SM4 的JAVA实现(基于BC实现)
  5. 玉雕分几级(不要当真,纯属调侃)
  6. python pypy_Python之父的加速秘籍:PyPy能让代码运行得更快!
  7. 两大热门解释器比较——PyPy和CPython的区别(官方文档翻译)
  8. 饥荒时用java写的吗_【图片】类与对象面向对象编程【饥荒mod制作吧】_百度贴吧...
  9. 怎么去选择一个合适的钱包
  10. python-批量提取srt文件中的纯文本