深入Linux网络子系统04:accept如何从全连接队列取出TCP连接

网络编程中操作系统连接队列管理:Linux TCP队列深度解析 操作系统对TCP连接队列(特别是SYN队列和Accept队列)的管理,对于网络应用程序的稳定性、性能和安全性至关重要。这些队列充当关键的缓冲区,在TCP三向握手期间管理传入连接的状态转换,并将内核的网络处理与应用程序对已建立连接的消费解耦。深入理解这些队列、其相关的内核参数()及其动态相互作用,对于网络专业人员来说是不可或缺的。适当的调优和监控对于防止连接丢弃、缓解拒绝服务攻击以及确保服务器应用程序在各种负载条件下的最佳性能和响应能力至关重要。 阅读详情


前言

从监听套接字的全连接队列中取出一个已完成三次握手的连接,返回其对应的套接字;如果队列为空,阻塞等待或非阻塞模式下立即返回-EAGAIN,看似简单的过程也隐藏着一些细节:

  1. 监听 socket 和 accept 返回的新 socket 有什么区别?
  2. 阻塞与非阻塞模式下 accept 行为有什么差异?
  3. accept 参与三次握手吗?

一、用户态 accept() 基础

1.1 函数原型

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数详解:

  • sockfd:监听套接字的文件描述符
  • addr:传出参数,存储客户端IP和端口
  • addrlen:传入传出参数, 传入 addr 缓冲区大小,调用结束后返回实际地址结构体长度

返回值:成功返回一个新的套接字文件描述符;失败返回-1

重要区分:

  1. sockfd: 监听 socket,专门用来接收新连接,不负责收发业务数据
  2. 返回的fd:已连接 socket,专门用来和客户端通信,收发数据

1.2 业务层简单示例

// 创建socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 绑定IP和端口
int ret = bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

// 打开监听
ret = listen(sockfd, 128);

struct sockaddr_in client_addr;
socklen_t cli_len = sizeof(client_addr);
// 等待客户端连接
int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cli_len);
// 使用 connfd 与客户端通信

二、accept调用过程

2.1 系统调用入口

关键行为:

  1. 根据文件描述符查找监听套接字
  2. 分配一个新的套接字(newsock)用于表示客户端的连接
  3. 新套接字继承监听套接字的类型(如SOCK_STREAM)和操作函数集
  4. 分配一个空闲的文件描述符(newfd),将其与新创建的套接字关联起来
  5. 执行协议层的 accept 操作
  6. 如果提供了地址缓冲区,获取客户端地址

源码(节选):

// net/socket.c
SYSCALL_DEFINE4(accept4, int, fd, ......)
{
    struct socket *sock, *newsock;
    struct sockaddr_storage address;
    // 根据文件描述符查找监听套接字
    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    // 分配一个新的套接字,表示客户端的连接
    newsock = sock_alloc();
    // 新套接字继承监听套接字的类型(如SOCK_STREAM)和操作函数集
    newsock->type = sock->type;
    newsock->ops = sock->ops;
    // 分配一个空闲的文件描述符,将其与新创建的套接字关联起来
    newfd = get_unused_fd_flags(flags);
    newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);
    // 实际调用 inet_accept
    sock->ops->accept(sock, newsock, sock->file->f_flags);
    // 获取客户端地址
    if (upeer_sockaddr)
    {
        newsock->ops->getname(newsock, (struct sockaddr *)&address,&len, 2);
        move_addr_to_user(&address, len, upeer_sockaddr, upeer_addrlen);
    }
    fd_install(newfd, newfile);
}

2.2 inet_accept

关键行为:

  1. 从全连接队列中取出一个已完成三次握手的子套接字
  2. 把获取到的子套接字关联到到前面新分配的用户态套接字上
  3. 将新分配的用户态套接字状态设置为已连接状态

源码(节选):

// net/ipv4/af_inet.c
int inet_accept(struct socket *sock, struct socket *newsock, int flags)
{
    // sock: 监听 socket  sk1: 监听 sock
    struct sock *sk1 = sock->sk;
    // 实际调用 inet_csk_accept ,从全连接队列中取出一个已完成三次握手的子套接字
    struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err);
    // 把获取到的子套接字关联到前面新分配的用户态套接字上
    sock_graft(sk2, newsock);
    // 将新分配的用户态套接字状态设置为 SS_CONNECTED(已连接)
    newsock->state = SS_CONNECTED;
}

2.3 inet_csk_accept

关键行为:

  1. 状态判断:如果监听套接字不处于监听状态,直接退出
  2. 如果连接队列为空,计算等待超时时间:非阻塞返回0;阻塞返回 MAX_SCHEDULE_TIMEOUT,无限等待
    1. 非阻塞:直接返回 -EAGAIN
    2. 阻塞:调用 inet_csk_wait_for_connect 让进程休眠,直到有连接到达或超时
  3. 队列不为空,从队列头部取出一个 request_sock并返回,这表示一个已完成握手的连接请求

源码(节选):

// net/ipv4/inet_connection_sock.c
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
{
    // sk:监听 sock
    struct inet_connection_sock *icsk = inet_csk(sk);
    // 全连接队列
    struct request_sock_queue *queue = &icsk->icsk_accept_queue;
    struct request_sock *req;
    struct sock *newsk;
    // 如果监听套接字不处于监听状态,直接退出
    if (sk->sk_state != TCP_LISTEN)
        goto out_err;
    // 判断连接队列为空
    if (reqsk_queue_empty(queue)) {
        // 计算等待超时时间
        // 1. 非阻塞返回0
        // 2. 阻塞返回 MAX_SCHEDULE_TIMEOUT(无限等待)
        long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
        error = -EAGAIN;
        // 非阻塞,直接返回 -EAGAIN
        if (!timeo)
            goto out_err;
        // 阻塞等待,进程睡眠,直到有连接到达或超时
        error = inet_csk_wait_for_connect(sk, timeo);
        if (error)
            goto out_err;
    }
    // 从队列头部取出一个 request_sock,这表示一个已完成握手的连接请求
    req = reqsk_queue_remove(queue, sk);
    newsk = req->sk;
    ......
    return newsk;
    ......
}

三、 常见错误码与排查命令

3.1 常见错误码

错误码含义常见原因
EAGAIN / EWOULDBLOCK非阻塞 socket,全连接队列为空非阻塞模式下没有就绪连接
EINTR系统调用被信号中断阻塞等待期间收到信号
EINVALsocket 不支持 acceptsockfd 没有执行 listen或者不是 SOCK_STREAM
EMFILE进程文件描述符达到上限fd 耗尽,无法分配新 fd
ENFILE系统文件描述符达到上限系统级打开文件数超限
ECONNABORTED连接已被中止握手完成后连接被 RST,部分内核版本会返回此错误

3.2 排查命令

# 查看进程 fd 使用数
ls /proc/<pid>/fd | wc -l
# 查看进程 fd 上限
cat /proc/<pid>/limits | grep "open files"
# 查看系统级打开文件数
cat /proc/sys/fs/file-nr

查看当前队列情况:

ss -antp
状态Recv-QSend-Q
LISTEN全连接队列当前数量全连接队列的最大容量
ESTAB接收缓冲区中未读取的数据字节数发送缓冲区中未发送的数据字节数

四、总结

  1. accept 不参与三次握手,只是消费全连接队列中内核已经握手完成的连接;
  2. 返回全新的已连接套接字 fd,监听 socket 本身不受影响,继续接收新连接;
  3. 阻塞模式队列为空会睡眠等待;非阻塞模式直接返回 EAGAIN。
深入 Linux 内核:accept() 函数如何从已完成连接队列创建新 Socket 本文深入解析Linux内核中accept()函数的工作原理,揭示其如何从已完成连接队列创建新Socket。通过分析TCP连接建立过程、内核调用链及队列管理机制,帮助开发者理解网络编程底层实现,优化高并发场景下的服务器性能。 阅读详情

相关推荐

3.1 计算机视觉的发展和卷积神经网络概要(百度架构师手把手带你零基础实践深度学习原版笔记系列)

3.1 计算机视觉的发展和卷积神经网络(百度架构师手把手带你零基础实践深度学习原版笔记系列) 概要 计算机视觉作为一门让机器学会如何去“看”的科学学科,具体的说,就是让机器去识别摄像机拍摄的图片或视频中的物体,检测出物体所在的位置,并对目标物体进行跟踪,从而理解并描述出图片或视频里的场景和故事,以此来模拟人脑视觉系统。因此,计算机视觉也通常被叫做机器视觉,其目的是建立能够从图像或者视频中“感知”信息的人工系统。 计算机视觉技术经过几十年的发展,已经在交通(车牌识别、道路违章抓拍)、安防(人脸闸机.

aiAI 691

Linux网络——连接、TCP全连接队列&&TCPdump抓包

本文通过实验探究了TCP服务器中listen()函数的第二个参数backlog的作用。实验发现当服务器不调用accept时,客户端仍能完成三次握手建立连接,但已建立连接数存在上限(backlog+1)。backlog参数控制了全连接队列的长度,该队列用于存储已完成三次握手但未被accept的连接。文章详细分析了Linux内核中TCP连接管理的实现机制,包括半连接队列和全连接队列,以及底层数据结构如tcp_sock、inet_connection_sock等的组织关系。实验还使用tcpdump抓包分析了TCP

m0_74515566的博客 1889

无人自行车平衡控制:最小二乘法动态零点拟合实践

本文探讨了无人自行车平衡控制中动态零点的重要性及实现方法。通过最小二乘法拟合车头角度与ROL值的线性关系,解决了固定零点在转向时导致的稳定性问题。实践表明,动态零点拟合能显著提升系统平衡稳定性并降低能耗。

uran的专栏 661

从内核代码角度理解TCP协议之全连接队列

本文通过实验代码探索了TCP listen()函数中backlog参数的作用。实验设置了一个TCP服务器,将backlog值设为1并注释掉accept()调用,使连接请求积压在全连接队列中。同时编写客户端程序模拟多连接请求。通过观察连接建立情况,可以直观理解backlog参数控制的是服务器全连接队列的最大长度。当队列满时,新连接请求会被拒绝或丢弃。文章从内核角度探讨了全连接队列的工作原理,为深入理解TCP三次握手后的连接管理机制提供了实践基础。

htw250056的博客 1583

1.深入浅出:理解三极管截止、放大和饱和状态!——参考《模拟电子技术基础》清华大学华成英主讲

幼儿园水平理解三极管截止、放大和饱和状态!书上看不懂,听课听不懂的过来! 绕不开的三极管结构 以NPN为例,晶体三极管的结构,这是很多人不想看的,但是确实是非常重要的!不看结构是理解不了工作原理的! (这样记忆:N是negative,负,代表多子为电子;P是positive,正,代表多子为空穴) 注意观察三极管的结构,有助于理解工作时的状态。 两张图结合起来看,略作解释: 1.图中空心为空穴带正...

Hardware_harder的博客 4万+

FPGA控制DDR读写(AXI4总线接口)

FPGA控制DDR读写

qq_42712308的博客 1万+

【Linux】从内核角度理解 TCP 的 全连接队列(以及什么是 TCP 抓包)

当服务器来不及进行accept时,底层的TCP listen sock依然允许建立三次握手,建立连接成功,但这个建立连接的数量是有限的:backlog + 1,这个长度就是全连接队列的长度。另外,pcap 后缀的文件与 PCAP(Packet Capture) 文件格式相关,即用于捕获网络数据包的文件格式。使用 -w 选项可以将捕获的数据包保存到文件中, 以便后续分析。上面的图片基本揭示了当创建一个文件描述符后,是如何建立连接读取数据的。是一款强大的网络分析工具, 主要用于捕获和分析网络上传输的数据包。

卜及中的博客 1222

手把手教你用Microsoft AEC Challenge数据集训练自己的回声消除模型(附完整代码)

本文详细介绍了如何使用Microsoft AEC Challenge数据集训练专业的回声消除模型,包括环境准备、数据清洗、模型架构设计、训练流程和实际部署注意事项。通过完整的代码示例和实战指南,帮助开发者快速掌握AEC技术,提升语音通信系统的回声消除效果。

weixin_27697385的博客 460

TCP全连接队列与 tcpdump 抓包

TCP 全 连 接 队 列 与 tcpdump 抓包

2301_77509762的博客 1313

【TCAD】Sentaurus 中的“陷阱trap”仿真设置

允许使用有限的数据集名称 SFactor 规范,例如 DeepLevels , xMoleFraction , 和 yMoleFraction (从兴奋剂档案中读取) eTrappedCharge 和 hTrappedCharge (从指定的文件中读取 DevFields 在 File 部分)或 PMI 用户字段 PMIUserField0...10 (从指定的文件中读取 PMIUserFields 在 File 部分)。给定的陷阱截面 eNeutral 陷阱是多种多样的 Xsec 项目参数。

荷塘阅色 1308

【乐企-工具篇】生成发票左上角二维码

【代码】【乐企-业务篇】生成发票左上角二维码。

九七的博客 708

【Linux网络】基于TCP的全连接队列与文件、套接字、内核之间的关系

套接字是网络通信的基本接口,允许程序发送和接收数据。在服务器端,监听套接字用于接受客户端的连接请求。一旦客户端请求到达并完成TCP三次握手,请求会被放入全连接队列,等待服务器通过`accept()`调用来处理。内核在此过程中扮演核心角色,管理套接字的生命周期,维护全连接队列,并处理网络数据的传输。简而言之,套接字是通信的端点,全连接队列是已完成握手的连接请求的缓冲区,而内核则负责协调和管理这些操作,确保网络通信的顺利进行。

why 1448

站在评审的角度写国自然基金本子,搭配这5条AI提示词,帮你一键提升本子中标率

多数同仁的选题失败,不是因为你的idea不好,而是你的逻辑没有立起来,你必须让评审看到,你是怎么从问题走向方法的。请你按照“同行做到哪、瓶颈卡在哪、我该怎么破”的逻辑,将我的创新点进行重构:【粘贴你原始的创新点描述】,要求:表达需结构化、可量化、可验证,突出对比后的实质性突破。与其等着评审指出问题,等着本子被拒,不如让AI提前模拟评审,帮你从可行性、创新性、国家价值、团队能力四个维度给你打分,并指出项目的致命破绽,提示词这样设计。创新点如果写不好,本子往往也会被拒,而评审一般喜欢的创新点都是有固定结构的。

智能AI让学术科研更容易 1241

【Linux网络编程】TCP全连接队列

TCP全连接队列(原理 + 内核)

2301_80277275的博客 938

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

【Linux网络】深入拆解 TCP 协议:标志位、紧急机制与连接管理的底层真相

我们每天使用的 HTTP、SSH、FTP 等应用,都建立在 TCP 协议之上。作为传输层最核心的可靠传输协议,TCP 的复杂性往往被上层应用的便捷性所掩盖。很多开发者能说出 “三次握手、四次挥手”,但对 RST 复位、PSH 推送、URG 紧急这些标志位的真实作用一知半解,更不清楚 accept 函数其实不参与三次握手、listen 的 backlog 参数到底控制什么。本文将从 TCP 报头剩余标志位入手,深度解析紧急指针的工作机制与常见坑点,再通过内核视角拆解 TCP 可靠性的三大核心策略,最后结合可运

会当凌绝顶,一览众山小 3216

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

TCP 全连接队列 内核层理解socket

参数表示。那什么是全连接队列呢?accept()accept()accept()也就是说我们访问服务器会由内核自动进行3次握手,,服务端再。而listen的,如果服务器来不及调用accept()处理连接,连接会堆积在超过最大连接长度,再进行连接就会三次握手失败。所以全连接队列本质就是一种backlog不能太长也不能太短。1.太短,可能丢失大量连接(客户端需要重试,增加网络负担)。2.太长,一方面会让用户,另一方面会。

wws7920的博客 1386

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

TCP全连接队列与抓包

再谈三次握手 ,SYN的应答是谁发起的, listen的第二个参数、全连接队列与半连接队列、Linux下的抓包、windows下的抓包

2301_79551553的博客 952

TCP全连接队列与tcpdump抓包

本文通过TCP实验探究了listen()函数的第二个参数(backlog)对连接队列的影响。实验发现,当服务器设置backlog=1且不调用accept()时,第四个客户端连接会处于SYN_RECV状态而非ESTABLISHED状态,揭示了Linux内核使用两个队列管理TCP连接:半连接队列(SYN队列)和全连接队列(accept队列),其中全连接队列长度为backlog+1。文章还介绍了tcpdump工具的使用方法,包括基本命令、过滤选项和文件操作,以及如何用Wireshark分析TCP通信流程(三次握手

2302_79737429的博客 1214

网络:没有accept(),能建立TCP连接吗

上面这个动图,是我们平时客户端和服务端建立连接时的代码流程。 对应的是下面一段简化过的服务端伪代码。 int main() { /*Step 1: 创建服务器端监听socket描述符listen_fd*/ listen_fd = socket(AF_INET, SOCK_STREAM, 0); /*Step 2: bind绑定服务器端的IP和端口,所有客户端都向这个IP和端口发送和请求数据*/ bind(listen_fd, xxx); /*St.

OceanStar的博客 771

《Linux 内核中的 TCP 队列:半连接队列与全连接队列的工作原理》

半连接队列和全连接队列是 Linux 内核 TCP 栈的核心组件,确保连接建立高效可靠。半连接队列管理未完成握手请求,而全连接队列缓冲已建立连接,等待应用程序处理。合理配置参数(如和backlog)可提升服务器性能和抗攻击能力。实际中,需结合系统监控工具优化设置,避免队列溢出导致的连接失败。

tdghjkld的博客 627

TCP全连接队列

全连接队列(accept队列):Linux内核为LISTEN监听套接字维护的队列。已经完成三次握手、状态为 ESTABLISHED,但是应用程序还没有调用accept()取走的TCP连接。半连接队列(SYN队列),存放只收到SYN、还没完成三次握手(SYN_RCVD)的连接。

weixin_73914025的博客 345

Linux网络编程中connect()、listen()和accept()与TCP三次握手的关系

Linux网络编程开发分为服务器端和客户端两个部分: 对于服务器端的流程——类似于接电话过程 socket()[找到一个可以通话的手机]----->bind()[插入一个固定号码]------>listen()[随时准备接听]-------> accept------->recv()------->send()------>close(); 对于客户端的主...

Magnum的博客 4612

深入Linux网络编程:accept函数——连接请求的“摆渡人”

Linux网络编程中,accept函数是TCP服务器的核心组件,负责从已完成连接队列中取出客户端请求并创建专用通信套接字。本文深入解析accept的工作原理,包括其作为“连接受理台”的定位、内核维护的SYN和ACCEPT双队列机制,以及阻塞/非阻塞两种模式的区别。通过代码示例展示了accept的基础用法,详细说明其参数、返回值和错误处理。文章还探讨了性能优化策略,帮助开发者掌握这一关键网络编程接口,构建高效的TCP服务器。

软件科学-郝学胜 1373

一文读懂全连接队列

我们在调用 listen 时,主要创建了全连接(accept queue)和半连接(syn queue)队列。全连接队列的大小取决于 min(backlog, somaxconn)不同版本对于全连接队列溢出的策略是不同的,但本质都是通过丢弃 Client 请求来保证 Server 的稳定。全连接队列主要供 accept 调用,因此只要使用先进先出的队列结构即可。全连接队列的数据主要来自于半连接,需要根据不同的 Client 获取,因此被设计成查询高效的哈希表结构。

BASK2311的博客 798

Linux网络(六):一文吃透 TCP Socket 编程:从监听状态到 accept 握手全流程

文章首先梳理 TCP 套接字创建与地址绑定的基础流程,深入解析 `socket()` 与 `bind()` 的参数含义及初始化规范;接着重点对比 UDP,详细讲解 TCP 独有的 `listen()` 函数、监听状态机制(`LISTEN`)以及使用 `netstat` 等工具查看网络连接状态的方法;最后通过通俗易懂的“鱼庄拉客”生活类比,深度拆解 `accept()` 函数的工作原理,厘清“监听套接字”与“服务套接字”的分工区别,并分析单进程 TCP 服务器模型的瓶颈所在。

小此方的博客 2489

五十三.网络(3)--区块同步getblocks消息

在节点互发了version消息后,就会发送getblocks消息,请求对方的区块以进行同步。

d3582077的专栏 182
上一篇: 深入Linux网络子系统03:调用 listen 之后,内核发生了哪些事?
下一篇: 05. 详解 connect 系统调用,告别三次握手疑难故障
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值