基础回顾----listen函数的backlog参数的本质

本文详细解析了网络编程中listen函数的backlog参数,阐述其与半连接队列和全连接队列的关系,通过实验验证理论,探讨全连接队列满时的处理机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在初学网络编程这块时,对listen函数的第二个参数(backlog)简单理解为服务器允许同时连接的客户端个数,但后面总是感觉对这个参数的意义很模糊,在查阅资料后才发现这个参数远远没有表面上那么简单,因此写篇博客总结、记录、巩固一下。

先提出两个概念半连接状态队列(syns queue)和完全连接状态队列(accept queue),通过下图可以看到这两个队列的作用:
在这里插入图片描述
图片来源:关于TCP 半连接队列和全连接队列

可以看到linux会将处于SYN_RCVD状态的socket放在半连接队列中,将已经建议好连接处于ESTABLISHED状态的socket从半连接队列拿出放在全连接队列中,accept系统调用返回的socket是从全连接队列中获取的。

现在我们可以讨论backlog的作用了,在内核版本2.2之前,backlog参数是指半连接队列和全连接队列的大小。在2.2版本之后,backlog参数只表示全连接队列的大小(accept队列有一个上限值,由系统参数somaxconn指定,也就是全连接队列的大小等于min(somaxconn, backlog)),半连接队列的大小为max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)。backlog的经典值为5。

实验:
上述理论我们可以通过代码来验证:

#include<stdio.h>
#include<sys/socket.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<arpa/inet.h>
int main(int argc,const char *argv[])
{
	if(argc < 3)
	{
		printf("usage:%s ip_address port_number\n",argv[0]);
		exit(1);
	}

	const char* ip = argv[1];
	int port = atoi(argv[2]);

	struct sockaddr_in listenaddr,connaddr;
	socklen_t len = sizeof(connaddr);
	listenaddr.sin_family = AF_INET;
	listenaddr.sin_port = htons(port);
	inet_pton(AF_INET,ip,&listenaddr.sin_addr);

	int listenfd = socket(AF_INET,SOCK_STREAM,0);
	
	bind(listenfd,(struct sockaddr*)&listenaddr,sizeof(listenaddr));

	listen(listenfd,3);

	sleep(30);//睡眠20秒,以便积累多个客户端连接
	while(true)
	{
		//接受客户端连接,从全连接队列中取出一个socket
		int connfd = accept(listenfd,(struct sockaddr*)&connaddr,&len);
		printf("hava a accept\n");
		sleep(10);//每10秒从全连接队列中取出一个socket
	}
	return 0;
}

上述代码在服务器调用listen后,先不accept,这样多个客户端连接过来,我们便可以通过netstat命令清楚地看到可以有多少个ESTABLISHED状态的连接即全连接队列的长度

当有七个连接请求到达时会出现下面这种情况。
在这里插入图片描述
我们可以看到对于服务端来说总共有4个处于ESTABLISHED状态的连接,说明全连接队列的长度是我们设置的backlog参数+1(不同的系统会有所不同,不过全连接队列的大小通常会比backlog值略大),但是server端并没有其他3个连接应有的SYN_RCVD状态,client端的状态停留在SYN_SENT状态,也就是说server并没有对这3个client发送SYN+ACK,这很怪异,因为server端理应发送SYN+ACK,并将这3个client的连接放到半连接队列中。通过tcpdump抓包可以看到,这3个client一直向server重发SYN请求,在多次重发SYN得不到回应时,会返回Connection timed out。
在这里插入图片描述

在这里插入图片描述

上述代码,每隔10s就会调用一次accept函数,也就是每隔10s就会从全连接队列中取出一个socket,这时全连接队列就会空出一个位置,这时server才会对到来的SYN回应建立连接,效果如下图:
在这里插入图片描述
在这里插入图片描述

貌似在全连接队列满后,server不会对再来的SYN做出响应,当全连接队列有空位时,server才会对到来的SYN响应建立连接,整个过程没看到server的SYN_RCVD状态,没有看到半连接队列的身影。这个结果并不符合理论。

对于上述问题,我查看了很多文档,也没有找到原因,如果有大佬知道,可以写在评论区,谢谢!

若全连接队列已经满了,此时服务端收到了一个客户端的ACK应答会发生什么(或者说有连接需要从SYN队列转移到accept队列)?

这个时候系统会根据/proc/sys/net/ipv4/tcp_abort_on_overflow内核参数的值来做出应对。

  1. 若tcp_abort_on_overflow值为0,则server会扔掉client发来的ACK应答,当定时器超时时,服务端会重传SYN+ACK给client(重新走三次握手的第二步),如果重传次数超过synack重传的阀值(/proc/sys/net/ipv4/tcp_synack_retries),则会把该连接从半连接队列中删除。
  2. 若tcp_abort_on_overflow值为1,server会发送rst给client,并删除掉这个连接。

client发送完ACK后就会进入ESTABLISHED状态,也就是connect函数返回,但是服务端因为全连接队列满了,所以对应连接实际没有准备好,这个时候如果client发数据给server,server会怎么处理呢?

我们看下面这个图片:
在这里插入图片描述
图片来源Linux协议栈accept和syn队列问题

150166号包是三次握手中的第三步client发送ack给server,然后150167号包中client发送了一个长度为816的包给server,因为在这个时候client认为连接建立成功,但是server上这个连接实际没有ready,所以server没有回复,一段时间后client认为丢包了然后重传这816个字节的包,一直到超时,client主动发fin包断开该连接。

所以通常情况下,应当把 tcp_abort_on_overflow 设置为 0,因为这样更有利于应对突发流量。因为client每次发送数据都会带一个对接受到server最近一次数据包的ACK应答,在这里就是server发送的SYN+ACK,所以若在client重传期间,server的全连接队列出现空位,则这条连接就会建立成功




参考:
阿里中间件团队博客:关于TCP 半连接队列和全连接队列
tcp的半连接与完全连接队列
https://www.cnblogs.com/xiaolincoding/p/13067971.html
Linux协议栈accept和syn队列问题
深入探索 Linux listen() 函数 backlog 的含义
http://blog.csdn.net/yangbodong22011/article/details/60468820

<think>好的,用户问的是“监听SOCKET监听的什么”。我需要先理解用户的问题。监听Socket在TCP网络编程中是一个基础概念,可能用户刚开始学习网络编程,或者对Socket机制不太清楚。 首先,我需要回顾之前用户的历史对话。上一次他们询问了为什么连接和通信不在同一个Socket上,我详细解释了监听Socket和通信Socket的分工。现在这个问题是进一步的,用户可能想更深入理解监听Socket的具体作用。 接下来,我需要确定“监听”具体指什么。监听Socket在TCP服务器中的作用是接受新的连接请求,也就是三次握手的过程。当服务器调用listen()函数后,Socket进入监听状态,等待客户端的连接请求。这时候,监听Socket并不处理数据传输,而是负责接收连接,然后通过accept()创建新的通信Socket来处理数据交换。 用户可能对监听的具体机制有疑问,比如监听的端口、IP地址,或者监听队列的概念。需要解释清楚监听Socket绑定的端口和IP,以及如何处理多个并发连接请求。比如,监听队列的大小,backlog参数的作用,还有内核如何处理这些挂起的连接。 另外,用户可能对监听Socket和通信Socket的区别还有疑惑,需要再次强调两者的不同职责。监听Socket只是负责接受连接,而每个连接建立后,通信Socket负责具体的数据传输,这样设计的好处是提高并发能力和系统资源的有效管理。 还要考虑用户可能的误区,比如是否监听Socket会处理数据,或者为什么不能直接用监听Socket通信。需要指出监听Socket没有建立具体的连接,无法进行数据传输,必须通过accept()创建新的Socket来处理。 可能用户在实际编程中遇到了问题,比如服务器无法接收连接,或者backlog设置不当导致连接被拒绝。这时候需要解释监听队列的工作原理,以及如何合理设置backlog参数,避免连接丢失。 最后,要确保语言通俗易懂,结构清晰,分点解释,帮助用户逐步理解监听Socket的作用,结合之前的回答,保持一致性,同时补充新的细节,比如内核维护的连接队列,SYN队列和ACCEPT队列的区别,以及如何处理半连接和全连接状态。 总结,用户的真实需求是理解监听Socket的核心功能,需要从原理、工作流程、参数设置、实际应用等方面全面解释,确保用户不仅能知道监听什么,还能理解背后的机制和设计原因。</think>在TCP网络编程中,**监听Socket(Listening Socket)** 的核心职责是**监听特定网络端口上的连接请求**。以下是具体解析: --- ### 一、监听的核心对象 #### 1. **监听的端口** - **绑定机制**:通过`bind()`系统调用绑定到特定端口(如Web服务常用80端口) - **端口独占性**:一个端口同一时间只能被一个监听Socket绑定 - **示例**: ```c server_socket.bind(("0.0.0.0", 8080)) // 绑定到所有网卡的8080端口 ``` #### 2. **监听的IP地址** - 通配地址(`0.0.0.0`):监听所有网卡接口 - 特定IP(如`192.168.1.100`):仅监听指定网卡 --- ### 二、监听的具体内容 #### 1. **TCP三次握手请求** - 监听Socket专门接收`SYN`包(连接请求) - 内核自动完成握手过程: 1. 收到客户端`SYN` → 回复`SYN-ACK` 2. 收到客户端`ACK` → 建立完整连接 #### 2. **维护连接队列** - **半连接队列(SYN队列)** 存储已完成第一次握手(`SYN_RECV`状态)的请求 大小由`/proc/sys/net/ipv4/tcp_max_syn_backlog`控制 - **全连接队列(ACCEPT队列)** 存储已完成三次握手(`ESTABLISHED`状态)的连接 大小由`listen(backlog)`参数决定 --- ### 三、监听工作流程 ```plaintext 客户端 服务器监听Socket | | | SYN (seq=x) | | ---------------------------------> | → 放入半连接队列 | | | SYN-ACK (seq=y, ack=x+1) | | <--------------------------------- | | | | ACK (ack=y+1) | | ---------------------------------> | → 移入全连接队列 | | | accept() | → 从队列取出连接 ``` --- ### 四、关键特性 1. **非阻塞性监听** - 使用`select()`/`epoll()`可同时监控多个监听Socket - 示例代码: ```c while(1) { int ready = epoll_wait(epfd, events, MAX_EVENTS, -1); for (处理每个就绪事件) { if (是监听Socket事件) accept(); // 接受新连接 else recv(); // 处理已有连接数据 } } ``` 2. **多协议支持** - 同一端口可同时监听TCP和UDP(需分别创建Socket) - 但TCP/UDP的监听Socket相互独立 --- ### 五、常见误区澄清 1. **监听 ≠ 数据接收** - 监听Socket仅处理连接建立,不参与后续数据传输 - 若强行用监听Socket发送数据: ```c send(listen_fd, data, len, 0); // 会返回ENOTCONN错误 ``` 2. **backlog参数误解** - 实际可用连接数 = `min(backlog, /proc/sys/net/core/somaxconn)` - Linux内核4.3+后采用`SYN Cookies`机制突破队列限制 --- ### 六、网络层实现细节 - **内核维护两个队列**: - **未完成连接队列**(SYN_RCVD状态) - **已完成连接队列**(ESTABLISHED状态) - **队列溢出处理**: -全连接队列满,内核会**忽略后续ACK包**(客户端将重试) - 使用`ss -lnt`命令可查看队列状态: ```bash State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:8080 *:* ``` - `Recv-Q`:当前ACCEPT队列中的连接数 - `Send-Q`:队列最大容量 --- ### 七、设计本质 监听Socket本质是**连接入口控制器**,其核心价值在于: 1. **连接准入控制**:通过队列机制实现流量整形 2. **资源预分配**:提前绑定端口/IP,避免连接建立时的资源竞争 3. **状态隔离**:确保新连接处理不影响已有连接 这种设计使得服务器能同时处理数万个连接请求(如Nginx的C10K问题解决方案),是构建高并发网络服务的基础
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值