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

TCP 全连接队列

理解 listen 的第二个参数

复制代码
int listen(int sockfd, int backlog);

backlog 参数表示 全连接队列(accept 队列)的最大长度
那什么是全连接队列呢?

三次握手 & accept() 处理流程

  1. 客户端发送 SYN ,服务器收到并放入半连接队列
  2. 服务器回 SYN+ACK ,客户端收到后发送 ACK
  3. 服务器收到 ACK,握手完成 ,连接进入全连接队列 等待 accept() 处理。
  4. 服务器调用 accept() 取出连接,建立 TCP 连接。
    也就是说我们访问服务器会由内核自动进行3次握手,完成后会连入全连接队列中 ,服务端再调用accept(),从队列中获取连接进行处理 。而listen的第二个参数就是全连接队列的最大连接长度 ,如果服务器来不及调用accept()处理连接,连接会堆积在**全连接队列。**超过最大连接长度,再进行连接就会三次握手失败。

所以全连接队列本质就是一种生产消费者模型backlog 不能太长也不能太短。

1.太短,可能丢失大量连接(客户端需要重试,增加网络负担)。

2.太长,一方面会让用户等待过长的时间 ,另一方面会占用大量内存

Linux 内核 TCP socket

先看一下从进程的文件描述符到 TCP socket的整体结构

1. 任务(task_struct)到文件描述符表

  • 进程的 task_struct 结构体包含 files_struct,用于管理进程的文件表。
  • files_struct 中有一个 fd_array[](文件描述符数组),存储着所有打开的文件(struct file 指针)。

2. 文件结构(struct file)

  • struct file 代表进程打开的一个文件,可能是普通文件、设备文件或者套接字(socket)。
  • 其中 private_data 用于存储 socket 相关信息。
  • 当文件描述符指向 socket 时,它的 private_data 指向 struct socket 结构体。

3. socket 结构(struct socket)

  • struct socket 代表一个高层的 socket 抽象,内部包含:

    • struct file *file:指向与 socket 关联的文件结构。
    • struct sock *sk:指向具体的 sock 结构体,代表底层协议相关的数据结构。
    复制代码
      struct socket {
          socket_state state;       // socket 当前状态,如 SS_CONNECTED
          short type;               // socket 类型,如 SOCK_STREAM(TCP)或 SOCK_DGRAM(UDP)
          unsigned long flags;      // socket 标志
          struct file *file;        // 关联的 struct file 结构体
          struct sock *sk;          // 关联的 struct sock 结构体(底层协议数据)
          const struct proto_ops *ops; // 指向 socket 操作函数
      };

4. sock 结构(struct sock)

  • struct sock 是底层 socket 内部的核心结构,存储协议无关的信息。
  • struct sock 可以指向不同的协议(如 TCP、UDP、RAW)扩展成更具体的协议结构。

5. TCP Socket结构

  • 从高层到底层,TCP socket 主要涉及以下结构:

    • struct sock(通用 socket 结构)
    • struct inet_sock(IP 层 socket)
    • struct inet_connection_sock(面向连接的 socket)
    • struct tcp_sock(TCP 连接的具体实现)

1 struct sock(核心 socket 结构)

struct sock 是 Linux 内核中最基础的 socket 结构,几乎所有的协议(TCP、UDP、RAW)都会用到它。它存储了 socket 相关的通用数据,例如:

复制代码
struct sock {
    struct socket *sk_socket;  // 关联的 socket 结构
    struct sock_common __sk_common; // 存放通用的网络信息,如 IP、端口等
    atomic_t sk_refcnt;  // 引用计数
    struct sk_buff_head sk_receive_queue; // 接收缓冲队列
    struct sk_buff_head sk_write_queue; // 发送缓冲队列
    struct proto *sk_prot; // 指向协议处理函数(TCP、UDP等)
};

关键字段解析

  • sk_socket:指向 struct socket,用于在高层 socket 结构和底层 sock 结构之间建立关联。
  • sk_receive_queue:存储接收到但尚未被应用层读取的数据包。
  • sk_write_queue:存储尚未发送的数据包。
  • sk_prot指向协议处理函数,例如 TCP 或 UDP 的操作集合。struct socket中type是TCP就调用TCP的函数

作用

  • struct sock 作为所有协议(TCP/UDP)的基类,包含通用 socket 操作。
  • struct socket 通过 sk 字段关联。
  • sk_prot 用于决定 socket 的具体协议操作,例如 tcp_prot(TCP 协议处理)。

2 struct inet_sock(IP 层 socket)

struct inet_sock 继承自 struct sock,用于 IPv4 相关的 socket 操作。这个结构体增加了 IP 层的字段,例如源 IP、目的 IP、端口号等。

复制代码
struct inet_sock {
    struct sock sk; // 继承 struct sock
    __be32 inet_saddr; // 本地 IP 地址
    __be32 inet_rcv_saddr; // 绑定的本地 IP 地址(可能是 0.0.0.0)
    __be16 inet_sport; // 本地端口(主机字节序)
    __be16 inet_dport; // 远程端口(主机字节序)
    __be32 inet_daddr; // 远程 IP 地址
    struct ip_options *inet_opt; // 额外的 IP 选项(如 IP 选路)
};

关键字段解析

  • inet_saddr:本地 IP 地址。
  • inet_daddr:目标 IP 地址。
  • inet_sport:本地端口号。
  • inet_dport:远程端口号。
  • inet_opt:可选的 IP 头部选项。

作用

  • 增加 IP 层信息,使得 struct sock 能够支持 IPv4 协议。
  • 存储 socket 关联的 IP 地址和端口号。
  • 供 TCP 和 UDP 继承,并扩展额外功能。

3 struct inet_connection_sock(面向连接的 socket)

struct inet_connection_sock 继承 struct inet_sock,用于 面向连接的协议(如 TCP)。它增加了 TCP 连接管理所需的数据,例如超时控制、连接状态等。

复制代码
struct inet_connection_sock {
    struct inet_sock icsk_inet; // 继承 struct inet_sock
    struct request_sock_queue icsk_accept_queue; // 半连接队列
    struct list_head icsk_ack_list; // TCP Fast Open 相关
    struct timer_list icsk_retransmit_timer; // 重传定时器
    struct request_sock *icsk_accept_head; // 指向全连接队列的头部
};

关键字段解析

字段 作用
icsk_inet 继承 struct inet_sock,包含 IP 地址、端口信息
icsk_accept_queue 半连接队列 ,存放 SYN-RECEIVED 连接
icsk_ack_list TCP Fast Open 机制,加快 SYN 建立连接
icsk_retransmit_timer TCP 重传定时器 ,用于 SYN+ACK 超时重传
icsk_accept_head 全连接队列 ,存放 ESTABLISHED 连接,等待 accept()

作用

  • 维护 TCP 连接状态(如 SYN、ESTABLISHED)。
  • 控制 TCP 连接的超时机制。
  • 监听 socket 维护 accept() 所需的连接队列。

4 struct tcp_sock(TCP 连接的实现)

struct tcp_sock 继承 struct inet_connection_sock,专门用于 TCP 连接,存储 TCP 专有的数据,例如 TCP 窗口大小、拥塞控制状态等。

复制代码
struct tcp_sock {
    struct inet_connection_sock inet_conn; // 继承 struct inet_connection_sock
    u32 snd_nxt; // 发送窗口的下一个序列号
    u32 rcv_nxt; // 接收窗口的下一个序列号
    u32 snd_una; // 发送窗口的最早未确认序列号
    u32 snd_wnd; // 发送窗口大小
    u32 rcv_wnd; // 接收窗口大小
    struct sk_buff *retransmit_skb_hint; // 指向要重传的数据
    struct tcp_congestion_ops *cong_control; // 拥塞控制算法
};

关键字段解析

  • snd_nxt:下一个要发送的 TCP 序列号。
  • rcv_nxt:期望接收的下一个 TCP 序列号。
  • snd_una:最早未被确认的 TCP 序列号(累计确认)。
  • snd_wnd / rcv_wnd:TCP 窗口大小,决定流量控制。
  • retransmit_skb_hint:指向当前需要重传的 TCP 报文。
  • cong_control:TCP 拥塞控制算法(如 Reno、CUBIC)。

作用

  • 维护 TCP 发送、接收窗口,实现流量控制。
  • 管理 TCP 拥塞控制机制,影响 TCP 传输速率。
  • 控制 TCP 数据包重传,保证可靠传输。
    结构体关系总结
  • struct sock(核心 socket 结构,通用于所有协议)。
  • struct inet_sock(增加 IP 地址、端口等)。
  • struct inet_connection_sock(增加 TCP 连接管理功能)。
  • struct tcp_sock(TCP 特有字段,如序列号、窗口大小等)

UDP Socket 结构层次

UDP(User Datagram Protocol)是无连接的传输协议,与 TCP 相比,它没有连接管理、流量控制和可靠性机制 ,因此在 Linux 内核中的实现相对简单。UDP 的 socket 仍然依赖于 struct sock,但不需要像 TCP 那样扩展 struct inet_connection_sockstruct tcp_sock

UDP 在内核中的数据结构层级如下:

  1. struct sock(核心 socket 结构)
  2. struct inet_sock(IP 层 socket,增加源/目标 IP、端口等信息)
  3. struct udp_sock(UDP 特有的数据结构)

TCP 需要 struct inet_connection_sockstruct tcp_sock 来处理连接管理,而 UDP 直接使用 struct inet_sock ,并在 struct udp_sock 中扩展少量的 UDP 相关字段。

listen()监听TCP连接创建tcp socket的内核过程

当服务器使用 listen() 监听 TCP 连接,并在 accept() 中接受新的连接时,Linux 内核会经历以下几个关键步骤:

  1. listen() 监听 TCP 端口
  2. 三次握手期间,创建 struct sock 并存入半连接队列,三次握手完成后连入全连接队列中
  3. accept() 取出 struct sock,创建 struct socket
  4. 创建 struct file 并存入 struct files_struct->fd_array[]
  5. listen():初始化监听 socket 并设置全连接队列

Linux 内核执行 tcp_listen_start(),配置 struct sock 进入监听状态。

listen() 作用

  • 设置 struct sock->sk_state = TCP_LISTEN
  • 初始化 icsk_accept_queue (半连接队列)和 icsk_accept_head(全连接队列)
  • 设置 backlog,决定全连接队列最大容量
    2. 客户端三次握手,创建 struct sock 并进入全连接队列

当客户端发起 TCP 连接时:

  1. 客户端发送 SYN

    • 服务器 TCP_LISTEN 状态,调用 tcp_v4_rcv() 处理 SYN
    • 服务器分配 struct request_sock,存入 半连接队列 (icsk_accept_queue)
  2. 服务器回复 SYN+ACK

    • 服务器仍然在 TCP_LISTEN 状态。
    • SYN+ACK 发送后,客户端需要回复 ACK
  3. 客户端发送 ACK,服务器进入 ESTABLISHED

    • tcp_v4_rcv() 处理 ACK
    • 服务器 创建 struct sock ,从半连接队列转移到 全连接队列 (icsk_accept_head) ,等待 accept() 处理。
      3. accept() 处理全连接队列,创建 struct socket

当服务器调用:accept()

  • accept() 取出全连接队列 icsk_accept_head 里的 struct sock
  • 创建 新的 struct socket 并关联 struct sock
  • accept() 创建 struct socket 后,还需要 struct file 结构,以便进程可以通过文件描述符访问它创建 struct file ,并存入 fd_array[](文件描述符表)。
    1.struct sock在三次握手过程中创建 并进入半连接队列

2.三次握手完成后 ,struct sock连接到全连接队列中,accpet()调用后创建对应的struct socket并用struct sock*sk关联,

3.再创建struct file 用private_data连接struct socket,struct socket中struct file*file再反指向file。

4.把file的指针 填入struct files_struct 中fd_array[]文件描述符表数组分配文件描述符fd。

5.再由accept()返回创建的fd

相关推荐
七夜zippoe4 小时前
CANN Runtime任务描述序列化与持久化源码深度解码
大数据·运维·服务器·cann
盟接之桥4 小时前
盟接之桥说制造:引流品 × 利润品,全球电商平台高效产品组合策略(供讨论)
大数据·linux·服务器·网络·人工智能·制造
会员源码网5 小时前
理财源码开发:单语言深耕还是多语言融合?看完这篇不踩坑
网络·个人开发
米羊1216 小时前
已有安全措施确认(上)
大数据·网络
Fcy6486 小时前
Linux下 进程(一)(冯诺依曼体系、操作系统、进程基本概念与基本操作)
linux·运维·服务器·进程
袁袁袁袁满6 小时前
Linux怎么查看最新下载的文件
linux·运维·服务器
主机哥哥6 小时前
阿里云OpenClaw部署全攻略,五种方案助你快速部署!
服务器·阿里云·负载均衡
ManThink Technology7 小时前
如何使用EBHelper 简化EdgeBus的代码编写?
java·前端·网络
珠海西格电力科技7 小时前
微电网能量平衡理论的实现条件在不同场景下有哪些差异?
运维·服务器·网络·人工智能·云计算·智慧城市
QT.qtqtqtqtqt7 小时前
未授权访问漏洞
网络·安全·web安全