注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。
tcb的创建流程
tcb的控制块对应的代码结构为struct tcp_sock ,当该结构体确认好各个参数时,我们就说tcb控制块建立了!
一、调用socket()时候
1.所有相关结构体(socket
、sock
、inet_sock
、tcp_sock
)的 内存都已分配(因为是嵌套结构, 一次性分配完整);
2.与 “套接字类型、协议族、通用操作” 相关的 核心参数已确定(如 socket->type
、sock->sk_family
、inet_sock->inet_protocol
);
3.但与 “具体地址 / 端口”(inet_sock
的地址端口)、“TCP 协议特有状态”(tcp_sock
的序列号、拥塞控制参数等)相关的参数,需要后续 bind()
、connect()
、listen()
等操作才能逐步确定。
4.简单说:“壳子(内存)在 socket()
时就有了,核心配置(通用参数)也定了,但细节(地址、TCP 特有状态)还得后续步骤填。”
二、struct socket、struct sock、struct inet_sock、struct tcp_sock四个结构体
1. 概念:
结构体 | 作用范围 | 核心功能 |
---|---|---|
struct socket | 所有套接字的顶层抽象 |
-关联用户态文件描述符( -存储套接字类型( -状态(连接 / 未连接),通过 |
struct sock | 全协议族通用核心 |
存储所有套接字的基础运行时状态: -收发缓冲区队列、等待队列(用于 -协议操作表( |
struct inet_sock | IPv4 协议族专用 |
扩展 IPv4 特有属性: -本地 / 对端 IP 地址( -端口( -TTL -衔接通用 |
struct tcp_sock | TCP 协议专用(基于 IPv4) |
扩展 TCP 特有属性: -序列号( -拥塞窗口( -连接状态( -重传定时器 -支撑 TCP 可靠传输(三次握手、重传、拥塞控制等)核心逻辑。 |
2. 四者的关系
2.1 层级逻辑:
从用户态接口(socket
)到内核通用核心(sock
),再到 IPv4 协议(inet_sock
),最终到 TCP 细节(tcp_sock
),逐层细化;
2.2 设计目的:
通过「通用 + 专用」分层,既保证跨协议族复用(如sock
),又能承载具体协议的复杂逻辑(如tcp_sock
的拥塞控制)。
2.3 实现方法:指针转换,利用首成员地址重合特性
因「首成员与结构体起始地址完全重合(偏移量 0)」,内核通过宏快速从通用结构定位专用结构:
struct socket
与 struct sock
:通过 socket->sk
直接关联(sock
是 socket
的成员指针)。
inet_sk(sk)
:从 struct sock* sk
转换为 struct inet_sock*
(通过sock
的首成员sk
定位);
tcp_sk(sk)
:从 struct sock* sk
先转inet_sock*
,再通过inet_sock
的首成员inet
定位 struct tcp_sock*
;
2.4 底层结构体代码
// 1. 顶层抽象:struct socket(用户态与内核态的桥梁)
struct socket {
socket_state state; // 套接字状态(如未连接、已连接、监听等)
unsigned short type; // 套接字类型(SOCK_STREAM/TCP、SOCK_DGRAM/UDP等)
const struct proto_ops *ops; // 协议操作表(绑定/连接/发送等底层实现)
struct sock *sk; // 关键指针:指向内核核心套接字结构(struct sock)
struct file *file; // 关联的文件结构体(通过文件描述符暴露给用户态)
};
// 2. 通用核心:struct sock(所有协议族的基础逻辑体)
struct sock {
struct sock_common __sk_common; // 通用属性(协议族、地址/端口占位等)
struct sk_buff_head sk_receive_queue; // 接收缓冲区队列
struct sk_buff_head sk_write_queue; // 发送缓冲区队列
struct wait_queue_head_t *sk_sleep; // 等待队列(用于I/O复用)
const struct proto *sk_prot; // 传输层协议操作表(如TCP/UDP的底层逻辑)
// 其他通用状态字段...
};
// 3. IPv4协议族专用:struct inet_sock(扩展IPv4特有属性)
struct inet_sock {
struct sock sk; // 第一个成员:嵌入通用sock结构(地址偏移为0)
__be32 inet_rcv_saddr; // 本地IPv4地址(网络字节序)
__be32 inet_daddr; // 对端IPv4地址(网络字节序)
__be16 inet_sport; // 本地端口(网络字节序)
__be16 inet_dport; // 对端端口(网络字节序)
int inet_ttl; // IPv4报文TTL(生存时间)
// 其他IPv4专属字段...
};
// 指针转换宏:从struct sock*获取对应的struct inet_sock*
// 原理:利用sk是inet_sock的首成员,地址完全重合
#define inet_sk(sk) container_of(sk, struct inet_sock, sk)
// 4. TCP协议专用:struct tcp_sock(扩展TCP特有属性)
struct tcp_sock {
struct inet_sock inet; // 第一个成员:嵌入IPv4的inet_sock结构(地址偏移为0)
u32 snd_nxt; // 下一个待发送的序列号
u32 rcv_nxt; // 下一个期望接收的序列号
u32 cwnd; // 拥塞窗口(拥塞控制核心)
u32 ssthresh; // 慢启动阈值
enum tcp_states tcp_state; // TCP连接状态(如ESTABLISHED、SYN_SENT等)
// 其他TCP专属字段(重传定时器、窗口机制等)...
};
// 指针转换宏:从struct sock*获取对应的struct tcp_sock*
// 原理:先通过inet_sk(sk)拿到inet_sock*,再利用inet是tcp_sock的首成员,地址完全重合
#define tcp_sk(sk) container_of(inet_sk(sk), struct tcp_sock, inet)
2.5 底层内核操控的实现代码
struct socket *sockt; // 假设已获取socket结构体指针
struct sock *sk = sockt->sk; // 直接通过sk指针访问struct sock
// 访问sock的参数:如接收队列长度
unsigned int rcv_len = sk->sk_receive_queue.qlen;
struct inet_sock *inet = inet_sk(sk); // 从sock*转换为inet_sock*
// 访问inet_sock的参数:如本地IP地址、端口
__be32 local_ip = inet->inet_rcv_saddr;
__be16 local_port = inet->inet_sport;
struct tcp_sock *tcp = tcp_sk(sk); // 从sock*转换为tcp_sock*
// 访问tcp_sock的参数:如发送序列号、拥塞窗口
u32 next_seq = tcp->snd_nxt;
u32 cwnd = tcp->cwnd;
三、tcb建立的流程
1.服务端和客户端的tcb建立时机
主体 / 场景 | 操作 / 阶段 | 结构体创建情况 | 说明 |
---|---|---|---|
服务端 - 监听套接字 |
掉用 |
立即创建完整组合:
| 用于后续接收客户端 SYN 包、管理监听队列(半连接 / 全连接队列),是服务端接收连接的基础结构。注意这个不是tcb没有建立,因为tcp_sock的参数填充不完整。 |
服务端 - 新客户端连接 | 第一次握手(收到客户端 SYN) | 仅生成临时结构 struct request_sock (半连接),不创建 sock /inet_sock /tcp_sock |
存储 SYN 包关键信息(序列号、MSS 等); 因连接未完全建立,用轻量临时结构节省内存。 |
服务端 - 新客户端连接 | 第三次握手(收到客户端 ACK) |
为新连接创建完整组合:
|
从 即tcb_sock参数配置完毕,tcb控制块建立! |
客户端 | connect() 时 |
基本配置好各结构体的参数(服务端的seq还没确立),tcb控制块建立! | 用于发起处理三次握手交互及后续数据传输,是客户端通信的核心结构。 |
2.与syn、accept队列的关联
队列类型 | 存储的核心数据结构(Linux 内核) | 对应连接阶段 | 关键信息 |
---|---|---|---|
syn_queue |
(半连接请求控制块) | 三次握手第一阶段(服务端收到 SYN,未完成握手) |
- 客户端 IP、端口; - 服务端初始序列号(ISN); - 连接状态( - 临时 TCP 选项(如窗口大小)。 |
accept_queue |
(4个结构体已经初始化并分配完整参数) | 三次握手完成后(服务端收到 ACK,连接建立), |
- 完整五元组(双方 IP、端口、协议); - 连接状态( - 收发缓冲区、拥塞控制参数、确认号 / 序列号; - 与套接字文件描述符(fd)的关联。 |
补充:
struct inet_request_sock是struct request_sock
在 IPv4 协议下的专用扩展,类似 inet_sock
对 sock
的扩展(注意:没有tcp_requeset_sock这个结构体,inet_request_sock足够记载半连接的信息了)
struct inet_request_sock {
struct request_sock req; // 第一个成员:嵌入通用 request_sock(地址偏移 0)
__be32 ir_loc_addr; // 本地 IPv4 地址(网络字节序)
__be32 ir_rmt_addr; // 客户端 IPv4 地址(网络字节序)
__be16 ir_loc_port; // 本地端口(网络字节序)
__be16 ir_rmt_port; // 客户端端口(网络字节序)
u32 ir_seq; // 服务器回复的 SYN+ACK 序列号
u32 ir_ack; // 服务器期望客户端回复的 ACK 序列号(ir_seq + 1)
// 其他 IPv4/TCP 握手专用字段...
};
四、细节补充:
1. 为什么accept_queue中存储的是sock?
答:
struct sock
是连接的核心标识:它包含了套接字的通用运行时状态(如连接状态、收发缓冲区、等待队列等),是内核管理连接的 “最小必要单元”。无论具体协议(如 TCP 基于 IPv4)如何,struct sock
都能统一表示一个连接的核心信息。
accept_queue
作为通用队列,只需存储最基础的 struct sock*
。
当需要访问协议专用属性时,内核可通过 inet_sk()
、tcp_sk()
等宏从 struct sock*
转换得到对应的专用结构体指针(如从队列中取出 sock*
后,用 tcp_sk(sock)
即可访问 TCP 特有参数)。
2. struct socket第三次握手时为什么没有创建?
答:
struct socket
是用户态与内核态的桥梁(关联文件描述符),但它并非连接的核心状态载体。当应用程序调用 accept()
时,内核会为队列中的 struct sock
动态创建对应的 struct socket
并关联,再返回给用户态。因此,accept_queue
中无需提前存储 struct socket
。
3. 元数据是什么?
3.1 定义
元数据(Metadata)的核心定义是:描述 “数据” 的数据(Data about Data)。它不直接承载业务层面的实际内容(如文件中的文本、图片像素、网络传输的文件内容),而是用于描述 “实际数据” 的属性、上下文、状态、管理规则等信息,目的是让 “实际数据” 能够被高效识别、定位、理解、使用和管理。
3.2 元数据的核心特征与作用
- 非载荷性:元数据本身不是用户需要的 “实际数据”(如你通过 TCP 发送的文件内容是 “数据载荷”,而描述这个 TCP 连接的 IP 地址、端口号是 “元数据”)。
- 描述性:聚焦于 “数据的属性”,比如数据的类型、大小、来源、创建时间、关联对象、状态等。
- 功能性:为数据的管理和交互提供支撑 —— 没有元数据,“实际数据” 会变成无法识别、无法操作的 “裸数据”(例如,没有文件名、文件大小的元数据,你无法在电脑上找到并打开一个文件;没有 IP 地址、端口的元数据,网络数据包无法送达目标主机)
3.3 不同场景下的元数据示例
元数据的具体形式随场景变化,以下是常见场景的例子,帮助你理解其通用性:
应用场景 | 实际数据(Data) | 元数据(Metadata)示例 |
---|---|---|
电脑文件系统 | 文档内容、图片像素、视频帧 | 文件名、文件大小、创建时间、修改时间、存储路径、文件格式(.txt/.jpg) |
数据库 | 表中的一行行业务记录(如用户信息) | 表名、字段类型(int/varchar)、主键、索引、数据创建时间、存储引擎 |
网络通信(TCP) | 传输的文件内容、API 请求体 | 源 IP 地址、目标 IP 地址、源端口、目标端口、TCP 连接状态(ESTABLISHED)、序列号、数据包长度 |
图片文件 | 图片的像素矩阵 | 图片分辨率(1920×1080)、色彩模式(RGB)、压缩格式(JPEG/PNG)、拍摄设备型号 |
4. 网络连接的元数据
元数据类别 | 具体元数据描述 | 对应内核结构体 | 核心作用 |
---|---|---|---|
基础标识类 | 套接字类型(如 SOCK_STREAM/TCP) | struct socket | 定义连接的传输模式(面向连接 / 无连接) |
协议族(如 AF_INET/IPv4) | struct sock | 确定网络层协议版本,适配地址格式 | |
传输层协议(如 IPPROTO_TCP) | struct inet_sock | 标识连接使用的传输层协议(TCP/UDP 等) | |
地址端口类 | 本地 IPv4 地址 | struct inet_sock | 标识本地通信端点的网络层地址 |
对端 IPv4 地址 | struct inet_sock | 标识对端通信端点的网络层地址 | |
本地端口号(网络字节序) | struct inet_sock | 标识本地进程的传输层端口 | |
对端端口号(网络字节序) | struct inet_sock | 标识对端进程的传输层端口 | |
状态控制类 | 套接字整体状态(如已连接 / 未连接) | struct socket | 反映套接字的全局状态(用户态可感知) |
TCP 连接状态(如 ESTABLISHED/SYN_SENT) | struct tcp_sock | 维护 TCP 协议特有的连接阶段(三次握手 / 四次挥手) | |
TCP 发送序列号(snd_nxt) | struct tcp_sock | 确保 TCP 数据传输的有序性和可靠性 | |
TCP 接收序列号(rcv_nxt) | struct tcp_sock | 标记下一个待接收的 TCP 数据序列,避免重复 | |
资源配置类 | 接收缓冲区队列(大小 / 长度) | struct sock | 暂存待用户读取的网络数据,协调 I/O 速度 |
发送缓冲区队列(大小 / 长度) | struct sock | 暂存待发送的网络数据,适配协议栈处理速度 | |
TCP 拥塞窗口(cwnd) | struct tcp_sock | 控制 TCP 发送速率,避免网络拥塞 | |
TCP 慢启动阈值(ssthresh) | struct tcp_sock | 切换 TCP 拥塞控制策略(慢启动 / 拥塞避免) | |
IPv4 报文 TTL(生存时间) | struct inet_sock | 限制 IP 报文在网络中的转发次数,防止环路 | |
TCP 重传超时时间 | struct tcp_sock | 触发 TCP 数据重传,保障传输可靠性 |
当用户调用socket的时候,实际上可以理解为给元数据的载体分配了内存。后续经过用户的操作和TCP的三次握手逐渐将元数据确定完整,当元数据确定完整之后,我们就说tcb控制块建立了!