
🎬 个人主页 :艾莉丝努力练剑
❄专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》
《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》
⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平
🎬 艾莉丝的简介:

文章目录
- 导入部分
- [1 ~> 学习目标](#1 ~> 学习目标)
- [2 ~> TCP 序列号知识点纠错与基础原理](#2 ~> TCP 序列号知识点纠错与基础原理)
-
- [2.1 序列号取值规则纠正](#2.1 序列号取值规则纠正)
- [2.2 序列号与 ACK 应答计算规则](#2.2 序列号与 ACK 应答计算规则)
- [2.3 通信起始序号随机化](#2.3 通信起始序号随机化)
- [3 ~> TCP 报头结构与标志位续讲](#3 ~> TCP 报头结构与标志位续讲)
-
- [3.1 TCP 标准报头基础](#3.1 TCP 标准报头基础)
- [3.2 网络通信时序细节](#3.2 网络通信时序细节)
- [3.3 RST 复位标志位](#3.3 RST 复位标志位)
- [3.4 PSH 推送标志位](#3.4 PSH 推送标志位)
- [3.5 URG 紧急标志位与 16 位紧急指针](#3.5 URG 紧急标志位与 16 位紧急指针)
-
- [3.5.1 基础定义](#3.5.1 基础定义)
- [3.5.2 16 位紧急指针原理](#3.5.2 16 位紧急指针原理)
- [3.5.3 易混淆点说明](#3.5.3 易混淆点说明)
- [4 ~> TCP 紧急数据编程实现](#4 ~> TCP 紧急数据编程实现)
-
- [4.1 Linux Socket 带外数据接口](#4.1 Linux Socket 带外数据接口)
- [4.2 C++ 紧急数据封装 Demo 代码](#4.2 C++ 紧急数据封装 Demo 代码)
- [4.3 多字节紧急数据系统限制](#4.3 多字节紧急数据系统限制)
- [5 ~> TCP 流量控制机制](#5 ~> TCP 流量控制机制)
-
- [5.1 缓冲区交互模型](#5.1 缓冲区交互模型)
- [5.2 窗口探测完整流程](#5.2 窗口探测完整流程)
- [6 ~> TCP 可靠性保障策略](#6 ~> TCP 可靠性保障策略)
-
- [6.1 确认应答(ACK)机制](#6.1 确认应答(ACK)机制)
- [6.2 超时重传机制](#6.2 超时重传机制)
-
- [6.2.1 丢包两大场景](#6.2.1 丢包两大场景)
- [6.2.2 超时时间动态计算](#6.2.2 超时时间动态计算)
- [6.2.3 指数退避重传算法](#6.2.3 指数退避重传算法)
- [6.2.4 多系统重传次数参数](#6.2.4 多系统重传次数参数)
- [7 ~> TCP 连接管理机制](#7 ~> TCP 连接管理机制)
-
- [7.1 三次握手进一步](#7.1 三次握手进一步)
-
- [7.1.1 核心认知](#7.1.1 核心认知)
- [7.1.2 对于核心认知版块的进一步阐释](#7.1.2 对于核心认知版块的进一步阐释)
- [7.1.3 三次握手核心作用](#7.1.3 三次握手核心作用)
- [7.2 连接内核本质](#7.2 连接内核本质)
- [7.3 connect 与 accept 函数底层本质](#7.3 connect 与 accept 函数底层本质)
- [8 ~> listen 全连接队列原理与上机实验](#8 ~> listen 全连接队列原理与上机实验)
-
- [8.1 实验环境配置](#8.1 实验环境配置)
- [8.2 实验所用代码](#8.2 实验所用代码)
-
- [8.2.1 backlog 参数与队列长度](#8.2.1 backlog 参数与队列长度)
- [8.2.2 修改Start函数:实验证明accept不参与三次握手](#8.2.2 修改Start函数:实验证明accept不参与三次握手)
- [8.3 全连接队列类比与作用](#8.3 全连接队列类比与作用)
- [8.4 Linux 内核全连接队列核心数据结构](#8.4 Linux 内核全连接队列核心数据结构)
- [9 ~> 拓展与收尾总结](#9 ~> 拓展与收尾总结)
-
- [9.1 拓展(关于AI话题)](#9.1 拓展(关于AI话题))
- [9.2 全文重点总结](#9.2 全文重点总结)
- 结尾

导入部分
一、整体详细思维导图

二、学习框架导入语
TCP 作为传输层最核心的可靠传输协议,不仅是网络编程、后端开发的基础,更是面试高频重难点。本节课作为 TCP 第三部分进阶内容,不再局限于基础的三次握手、四次挥手表层概念,而是深入 TCP 报头剩余标志位、序列号底层规则、紧急指针机制,同时完整拆解RST、PSH、URG 三大核心标志位的工作逻辑;在此基础上系统讲解 TCP 可靠性保障的两大支柱:确认应答机制、超时重传机制 ,剖析指数退避算法、多系统重传参数差异;再结合上机实验揭秘listen函数backlog参数、全连接队列的底层原理,纠正accept函数不参与三次握手的认知误区;还附带了 TCP 带外紧急数据的 C++ 代码封装实现,从理论原理、标志位解析、流量控制、可靠性策略、连接状态管理、内核队列结构到编程实践、上机实操形成完整逻辑闭环。无论是复盘网络底层原理、备战面试,还是落地网络编程开发,都能通过本内容完整梳理 TCP 深层运行机制。
1 ~> 学习目标
本文围绕 TCP 进阶核心知识点展开,核心学习目标依次为:补全 TCP 报头剩余字段,重点掌握 16 位紧急指针及关联标志位;讲解流量控制机制并引入滑动窗口基础认知;深度理解 TIME_WAIT、CLOSE_WAIT 两种连接状态,掌握 TIME_WAIT 引发 bind 失败的解决办法;完善 TCP 标志位 RST、PSH、URG 原理;梳理 TCP 流量控制、拥塞控制、延迟应答、捎带应答底层逻辑;理解 TCP 面向字节流特性与粘包问题根源;搭建 TCP 确认应答、超时重传、连接管理的完整可靠性体系;最后结合代码 Demo、上机实验掌握紧急数据收发、全连接队列底层原理。
2 ~> TCP 序列号知识点纠错与基础原理
2.1 序列号取值规则纠正
上节课序列号知识点存在表述错误:TCP 每个发送的数据段,以报文数据第一个字节作为序列号,而非最后一个字节。可将发送缓冲区抽象为逻辑数组,缓冲区中每一个字节对应数组的下标,TCP 以字节为单位进行编号管理。
2.2 序列号与 ACK 应答计算规则
主机 A 向主机 B 发送 1~1000 字节数据时,接收方返回的 ACK 确认序号遵循规则:ACK 应答序号 = 报文起始序号 + 数据自身长度,构成前闭后开的区间标识,代表该序号之前所有字节已全部接收。 示例:发送数据 1 ~ 1000 字节,应答 ACK 为 1001;后续发送 1001 ~ 2000 字节,应答 ACK 为 2001,以此类推。
bash
主机A 主机B
1~1000字节 ------->
<------- ACK=1001
1001~2000字节 ------->
<------- ACK=2001
2001~3000字节 ------->
<------- ACK=3001
规则:ACK 应答序号 = 报文起始序号 + 数据自身长度
前闭后开区间:收到 ACK=N 代表「N 之前所有字节全部收到,下次从 N 开始发」。
2.3 通信起始序号随机化
TCP 通信双方不会固定从序号 1 开始传输,通信时双方会各自生成随机起始序号 M、N,且互相感知对方起始序号。序号随机化的核心目的:规避网络中老旧残留数据包干扰当前正常通信。
3 ~> TCP 报头结构与标志位续讲
3.1 TCP 标准报头基础
bash
0 15 16 31
+-----------------------+-----------------------+
| 16位源端口号 | 16位目的端口号 |
+-----------------------+-----------------------+
| 32位序列号 |
+-----------------------+-----------------------+
| 32位确认序号 |
+-----+-----+-----------+-----------------------+
|4位首|6位保|6位标志位 | 16位窗口大小 |
|部长度|留位|URG/ACK/PSH|RST/SYN/FIN |
+-----+-----+-----------+-----------------------+
| 16位检验和 | 16位紧急指针 |
+-----------------------+-----------------------+
| 选项(最长40字节) |
+-------------------------------------------------+
| 数据载荷 |
+-------------------------------------------------+
TCP 标准报头固定长度为 20 字节,核心字段包含:16 位源端口号、16 位目的端口号、32 位序号、32 位确认序号、4 位首部长度、6 位保留位、6 位标志位、16 位窗口大小、16 位检验和、16 位紧急指针,后续选项字段按需扩展。

3.2 网络通信时序细节
网络通信时序图中通信线均为斜线,核心原因是隐含时间维度:双方收发数据存在网络传输耗时,时间线性递增,通信交互必然存在时间差。三次握手与四次挥手本质是同一种连接机制,捎带应答的存在是区分三次、四次挥手的核心原因,三次握手也可从逻辑上拆解为四次握手。
上节课核心结论:一是 TCP 通信交互最多的是仅带报头、无数据的报文;二是 TCP 报文划分不同类型,不同类型报文需触发不同处理逻辑。
3.3 RST 复位标志位
RST 全称为 reset 复位,携带 RST 标识的报文称为复位报文段,核心作用是通知对方重新建立连接。 三次握手存在特殊异常场景:客户端发送最后一个 ACK 报文后,自身默认三次握手完成、连接建立成功;但若该 ACK 报文丢失,服务端未收到 ACK,会判定三次握手未完成。此时客户端若直接发送业务数据,服务端会拒绝接收,并向客户端发送 RST 复位报文,要求客户端重新建立连接。 通过 RST 复位机制可指数级降低连接建立失败概率,单次失败概率 1%,两次重试后失败概率降至 1%×1%。
bash
客户端 服务端
SYN ------->
<------- SYN+ACK
ACK -------> 【ACK丢失】
客户端:连接建立成功
服务端:握手未完成
客户端发业务数据 ------->
服务端返回 RST <-------
客户端重新建立连接
RST 报文作用:连接异常、两端状态不一致时,强制重置连接、重新握手。
3.4 PSH 推送标志位
bash
应用层
↑↓
内核缓冲区(满/空闲)
↑↓
TCP传输层
↑↓
网络层
接收缓冲区满 → 窗口大小置0 → 发送端停止发送
发送端发窗口探测包 → 接收端应答带回窗口大小
多次探测窗口仍为0 → 置PSH=1
PSH作用:跳过OS水位线,强制应用层立刻读缓冲区数据
SSH、Telnet 默认带 PSH,催促对方立即处理交互命令。
TCP 流量控制的核心前提:发送方需要感知接收方缓冲区剩余容量,以此控制发送速率。 当接收方 TCP 缓冲区被数据打满,会在报文窗口大小字段填充 0,发送方收到后停止发送数据进入阻塞状态。发送端不会永久阻塞,会定时发送仅含 TCP 报头、无业务数据的窗口探测包 ,强制要求接收方应答,通过应答报文持续获取接收窗口最新大小。 若多次探测后接收窗口仍为 0,发送端会触发 PSH 标志位机制。PSH 标志位置 1 的核心作用:提示接收端应用程序立即从 TCP 内核缓冲区读取数据,无需等待操作系统默认的水位线唤醒机制。 PSH 并非仅在窗口为 0 时使用,SSH、Telnet 等命令行服务默认都会设置 PSH 标志位,强制要求对方尽快处理交互指令;其底层本质是跳过操作系统缓冲区默认唤醒策略,直接唤醒应用层读取数据。
3.5 URG 紧急标志位与 16 位紧急指针
3.5.1 基础定义
URG 标志位标识16 位紧急指针是否有效,99% 的常规通信场景下 URG 置 0,紧急指针无效;仅需传输紧急控制数据时 URG 置 1。 TCP 默认数据按序号按序到达、按序交付应用层,部分场景需要控制报文插队优先处理,例如百度云盘暂停 / 取消上传、SSH 终端 Ctrl+C 中断程序,这类指令无需等待历史数据处理完成,TCP 通过 URG + 紧急指针实现带外优先处理。
3.5.2 16 位紧急指针原理
bash
报文起始序号:1000
数据区总长:100字节
紧急指针值:10
紧急数据区间:1000 ~ 1009 (前10字节)
普通数据区间:1010 ~ 1099 (后90字节)
规则:
1. 起点:本报文数据第一个字节
2. 紧急指针=紧急数据长度
3. 仅作用于当前报文,不关联全局缓冲区
URG=1 时,接收方优先把紧急数据交给应用层插队处理,不用排队。
典型场景:SSH Ctrl+C 中断程序、云盘暂停取消上传控制指令。
紧急指针是 16 位偏移量,仅作用于当前 TCP 报文段的数据区内部,与全局缓冲区、整个传输流序列号无关。
- 固定起点:紧急数据永远从当前报文数据区第一个字节开始;
- 偏移量含义:紧急指针数值等于紧急数据的字节长度,指向紧急数据最后一个字节;
- 区间规则:起点到紧急指针指向位置的连续区间,全部为紧急数据,剩余为普通数据。
示例:报文起始序列号 1000,数据区共 100 字节,紧急指针值为 10;紧急数据为 1000~1009 字节,1010 及后续为普通数据。接收方检测到 URG=1 时,会优先将紧急数据递交应用层,无需在缓冲区排队。
3.5.3 易混淆点说明
接收方计算紧急数据末尾序列号时,会用「本报文序列号 + 紧急指针 - 1」,看似关联全局序列号,但紧急指针本身仅代表本报文内数据长度,后续报文的紧急数据由自身紧急指针独立定义,不会复用偏移量。
4 ~> TCP 紧急数据编程实现
4.1 Linux Socket 带外数据接口
Linux 系统调用中,send、sendto、sendmsg、recv、recvfrom、recvmsg均支持MSG_OOB标志位,用于收发 TCP 带外紧急数据:
MSG_OOB发送:在流式 Socket 中发送带外紧急数据,内核自动设置 URG 标志位与紧急指针;MSG_OOB接收:单独读取带外数据,不混入普通数据流。
4.2 C++ 紧急数据封装 Demo 代码
cpp
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/select.h>
#include <cstring>
// 发送紧急数据:先普通数据,再单字节OOB紧急数据
void sendUrgentData(int sockfd, const std::string normalData, char urgentByte)
{
ssize_t n = send(sockfd, normalData.data(), normalData.size(), 0);
if(n < 0)
{
perror("send normal data failed");
return;
}
std::cout << "[发送端] 成功发送普通数据:" << normalData
<< ",字节数:" << n << std::endl;
// 带外紧急数据
n = send(sockfd, &urgentByte, 1, MSG_OOB);
if(n < 0)
{
perror("send urgent data failed");
return;
}
std::cout << "[发送端] 成功发送紧急字节:'" << urgentByte
<< "',已设置URG标志位,紧急指针指向该字节末尾" << std::endl;
}
// 接收紧急数据:select监听异常事件
char recvUrgentData(int sockfd)
{
fd_set except_fds;
FD_ZERO(&except_fds);
FD_SET(sockfd, &except_fds);
std::cout << "[接收端] 正在等待紧急带外数据到达..." << std::endl;
int ret = select(sockfd + 1, nullptr, nullptr, &except_fds, nullptr);
if(ret < 0)
{
perror("select failed");
return 0;
}
if(FD_ISSET(sockfd, &except_fds))
{
char urgentByte = 0;
ssize_t n = recv(sockfd, &urgentByte, 1, MSG_OOB);
if(n < 0)
{
perror("recv urgent data failed");
return 0;
}
std::cout << "[接收端] 成功收到紧急字节:'" << urgentByte << "'" << std::endl;
return urgentByte;
}
return 0;
}
4.3 多字节紧急数据系统限制
TCP 理论上支持通过MSG_OOB发送多字节紧急数据,但 BSD Socket 及 Linux、Windows 主流系统实现中:
- 发送端可传入任意长度数据,内核会将整段数据放入 URG 标志位报文,紧急指针指向最后一个字节;
- 接收端通过
MSG_OOB仅能读取紧急数据最后一个字节 ,前面其余字节会混入普通数据流,通过常规recv读取; - 工程实践中,TCP 紧急数据默认仅用作 1 字节控制标记,多字节业务数据不建议使用带外机制,可采用双 Socket 方案分离普通数据与控制数据。
5 ~> TCP 流量控制机制
5.1 缓冲区交互模型
应用层通过write、read、send、recv等系统调用完成数据拷贝,本质是在应用层缓冲区、内核 TCP 发送 / 接收缓冲区之间拷贝数据;TCP 收发缓冲区分别位于通信双方操作系统内核,流量控制在两个传输方向双向生效。
bash
应用层 App
↓↑ 系统调用read/write/send/recv
应用层缓冲区
↓↑ 数据拷贝
内核发送缓冲区 / 内核接收缓冲区
↓↑
传输层TCP
↓↑
网络层 → 链路层
流量控制双向生效,核心靠「窗口大小」字段告知对方自己缓冲区剩余容量。
5.2 窗口探测完整流程
- 发送方根据 ACK 确认序号,明确后续起始发送位置,同时通过 16 位窗口大小字段感知接收缓冲区剩余容量;
- 接收缓冲区满时,窗口大小置 0,发送方停止发送业务数据;
- 超时未收到窗口更新报文,发送方主动发送无数据的窗口探测包;
- 接收方必须应答探测包,应答报文携带最新窗口大小,发送方以此更新发送策略;
- 若窗口更新报文丢失,发送方持续定时探测,避免通信永久阻塞。
bash
A发送数据 → ACK携带窗口大小=0 → A停止发送
超时无窗口更新 → A发窗口探测包(无数据仅报头)
B必须应答 → 带回最新窗口大小
若窗口更新报文丢失 → A定时持续探测,避免永久阻塞
6 ~> TCP 可靠性保障策略
6.1 确认应答(ACK)机制
TCP 将每个字节数据独立编号为序列号,每一段数据发送后,接收方返回携带确认序号的 ACK 报文,告知发送方已接收的数据范围、下一次起始发送位置。 ACK 报文可仅携带报头、无业务数据,由操作系统内核自动应答,无需应用层参与。若逐段等待 ACK 再发送下一段数据,在网络往返时延较大时性能极差,为后续滑动窗口、批量发送埋下铺垫。
TCP 给每个字节编序列号,每段数据对应一个 ACK。
ACK 可无数据,由内核自动应答,无需应用层参与。
6.2 超时重传机制
6.2.1 丢包两大场景
发送方发送数据后未收到 ACK,存在两种核心原因:
- 一是业务数据报文丢失,接收方未收到数据;
- 二是业务数据正常到达,但 ACK 应答报文丢失。
也就是:
bash
场景1:数据报文丢失
A发数据 → 网络丢包 → B收不到 → 无ACK → A超时重传
场景2:ACK应答丢失
A发数据 → B收到 → B回ACK → ACK半路丢失
A无收到ACK → 判定超时 → 触发重传
B靠序列号去重,丢弃重复报文
两种场景下发送方都会触发超时重传,接收方通过序列号识别重复报文,自动去重丢弃,保证上层无重复数据。
6.2.2 超时时间动态计算
超时时间不能固定:设置过大会降低传输效率,设置过小会引发无效重复重传。TCP 采用动态自适应超时时间,适配不同网络环境。
6.2.3 指数退避重传算法
Linux、BSD、Windows 系统均以500ms为超时基础单位,重传超时时间按指数递增:
- 首次超时等待 500ms 重传;
- 无应答则等待 2×500ms;
- 再次无应答等待 4×500ms,以此类推;
- 重传次数达到系统阈值后,判定网络或对端异常,强制关闭连接。
bash
第1次超时:等待 500ms 重传
第2次超时:等待 1000ms 重传
第3次超时:等待 2000ms 重传
第4次超时:等待 4000ms 重传
... 指数倍增
到达最大重传次数 → 强制关闭连接
6.2.4 多系统重传次数参数
- 数据传输阶段:Linux 由
tcp_retries2控制,默认 15 次;Windows 由TcpMaxDataRetransmissions控制,默认 5 次;超时总时长上限 13~30 分钟,超出则强制断连; - 连接建立阶段:Linux SYN 包重传由
tcp_syn_retries控制,SYN-ACK 由tcp_synack_retries控制,默认均为 5 次;Windows 由注册表对应参数控制。
补充:
- Linux 数据传输:
tcp_retries2默认 15 次 - Windows:
TcpMaxDataRetransmissions默认 5 次 - 握手阶段
SYN/SYN-ACK:Linux 默认 5 次
7 ~> TCP 连接管理机制
7.1 三次握手进一步
7.1.1 核心认知
三次握手完成仅代表自身视角认为连接建立成功,不代表两端真正达成共识。前两次 SYN、SYN+ACK 报文有应答保障,可靠性充足;第三次 ACK 报文无应答,属于 "赌对方能收到"。 第三次 ACK 丢失会造成两端认知割裂:客户端认为连接已建立,服务端认为握手未完成,客户端发数据时服务端返回 RST 复位报文。
7.1.2 对于核心认知版块的进一步阐释
bash
客户端:CLOSED → SYN_SENT → ESTABLISHED
服务端:CLOSED → LISTEN → SYN_RCVD → ESTABLISHED
第三次 ACK 无应答保障,丢失后客户端认为已连上,服务端认为未完成,靠 RST 修复。
7.1.3 三次握手核心作用
- 验证 TCP 全双工通信能力;
- 确认双方通信意愿,建立内核层面通信共识。
7.2 连接内核本质
TCP 连接不是物理线路,是内核维护的状态结构体,以文件描述符 fd 暴露给应用层。
TCP 连接本质是双方操作系统内核维护的专用数据结构体,每个结构体对应唯一连接状态,通过文件描述符暴露给应用层使用。核心状态流转:CLOSED → SYN_SENT → SYN_RCVD → ESTABLISHED → 数据传输 → 四次挥手 → TIME_WAIT → CLOSED。
7.3 connect 与 accept 函数底层本质
connect函数:由客户端调用,作用是主动发起三次握手;accept函数不参与三次握手 :三次握手由两端操作系统内核自动完成,accept仅负责把内核全连接队列中已完成握手的连接,取出并生成文件描述符交给应用层使用;- 即使应用层不调用
accept,内核依然可以完成三次握手,连接存入全连接队列。
8 ~> listen 全连接队列原理与上机实验
8.1 实验环境配置
CentOS7 安装 Telnet 命令:
bash
# 安装Telnet客户端
sudo yum install -y telnet
# 安装Telnet服务端
sudo yum install -y telnet-server
排查一下这些命令:IP、ip addr、netstat -natp | grep 端口。
通过ping测试主机网络连通性,ifconfig/ip addr查看本机内网 IP,netstat -natp | grep 端口号查看 TCP 连接状态。
8.2 实验所用代码
- 目录树结构

8.2.1 backlog 参数与队列长度

cpp
// 设置全连接个数
static const int gbacklog = 1; // 原本这里是32,改成1看看
listen(int fd, int backlog)第二个参数为 backlog,全连接队列长度 = backlog + 1 。 实验验证:设置gbacklog=1时,全连接队列最多缓存 2 个已完成三次握手的连接;第三个客户端发起连接会阻塞,处于 SYN_RCVD 状态,无法完成握手。
8.2.2 修改Start函数:实验证明accept不参与三次握手
cpp
void Start()
{
// // 最佳实践
// signal(SIGCHLD,SIG_IGN); // 直接对子进程退出信号做忽略
// 死循环
while(true)
{
// 全部注释掉,就搞一个死循环;加个sleep(1)
sleep(1);
// struct sockaddr_in clientaddr; // 客户端地址
// socklen_t len = sizeof(clientaddr); // 客户端地址长度,提供的缓冲区大小
// int sockfd = accept(_listsockfd,(struct sockaddr *)&clientaddr,&len); // 强转
// if(sockfd < 0)
// {
// // 张三推销被拒绝会一蹶不振吗?拉客失败最多warning,不影响后面的拉客动作,继续accept
// LOG(LogLevel::WARNING) << "accpet error";
// continue;
// }
// // 获取地址了,就知道客户端连接了,得获取一下(IP地址和端口号)
// InetAddr clientaddress(clientaddr);
// LOG(LogLevel::INFO) << "get a new link: " << clientaddress.StringAddress() << " sockfd: " << sockfd;
// // sleep(1);
// // 处理连接,进行IO通信
// // // Version0 -- 不会被使用的单进程版本
// // Service(sockfd, clientaddress);
// // Version1 -- 多进程版本优化
// // 今天我要实现一个长服务,我就不选择线程池版本了,不想处理短服务了
// // 要实现一个字典翻译、命令行解析的业务
// pid_t id = fork();
// if(id < 0) // 创建失败
// {
// LOG(LogLevel::ERROR) << "fork error";
// close(sockfd);
// }
// else if(id == 0)
// {
// // 子进程,拷贝文件描述符表,从而和父进程看到同一批文件
// if(fork() > 0) // > 0才是父进程退出
// exit(0);
// // 孙子进程
// Service(sockfd,clientaddress);
// exit(0);
// }
// else
// { }
// // 关闭没有时序问题,各管各的,不影响
// close(sockfd);
// // 父进程
// pid_t rid = waitpid(id,nullptr,0);
// (void)rid;
// // // Version2 -- 多线程版本
// // // --> 多线程这里不能关闭任何自己不用的sockfd <--
// // // 其实我大可以使用以前我自己封装的线程,但是整理我直接用原生的线程,这样会更直观
// // // 我不想创建进程,我要使用多线程降低创建进程的开销
// // pthread_t tid;
// // // pthread_create(&tid,nullptr);
// // // 以new的方式新建,其它线程也能够看见
// // ThreadData *td = new ThreadData(sockfd,clientaddress,this);
// // pthread_create(&tid,nullptr,Threadrun,(void*)td);
// // // // 这里的join是阻塞的!主线程不能卡在这里,主线程要继续去获取新链接才可以啊!
// // // pthread_join(tid,nullptr);
// // // Version3 -- 创建线程池(单例模式) -- 是bug吗?其实是应用场景方面的限制
// // // [](){} -- lambda
// // // 按值捕获 sockfd 和 clientaddress,捕获 this 指针
// // // 线程池不适合进行长服务,线程池适合短服务,这里TcpServer要进行长服务,实现命令行解析功能,就不用线程池了
// // ThreadPool<task_t>::GetInstance()->Enqueue([sockfd,clientaddress,this](){
// // Service(sockfd,clientaddress); // 这样线程池中的线程就能访问到客户端 socket 和地址信息
// // });
}
}

总之这里的结论就是:accept不参与三次握手,因为我把accept注释掉了,连接依然建立成功,所以三次握手成功,accept不参与三次握手。
8.3 全连接队列类比与作用
采用海底捞排队模型类比:服务端进程是就餐区域,全连接队列是门口排队等候区。
bash
餐厅就餐区 = 应用层服务进程
门口等候小板凳 = 全连接队列
客人完成登记(三次握手完成)→ 坐到等候区
餐厅有空位 → accept取走一个客人处理
队列不能太短:不够缓存
队列不能太长:客户端等待超时断开
最佳实践:backlog设16 / 32
- 全连接队列是内核层面的连接缓存 ,存放已完成三次握手、未被
accept取出的连接; - 作用:解耦内核与应用层,避免应用层处理缓慢时阻塞新连接建立,填补服务端处理空闲窗口期;
- 设计限制:队列不能过短,否则无法缓存连接;不能过长,否则客户端等待超时断开连接。工程最佳实践:
backlog设置为 16 或 32。
8.4 Linux 内核全连接队列核心数据结构
c
1. struct request_sock_queue 队列根结构(头/尾指针、锁)
2. struct request_sock 每个节点代表一个已完成握手连接
3. struct sock 监听socket,内嵌全连接/半连接队列
struct request_sock_queue:全连接队列根结构,包含队列头、队列尾、队列保护锁;struct request_sock:全连接队列节点,每一个节点对应一个已完成三次握手的连接;struct sock:监听 Socket 核心结构,内部引用全连接队列与半连接队列,是内核管理 Socket 的基础载体。
9 ~> 拓展与收尾总结
9.1 拓展(关于AI话题)
- AI 产业处于落地导入期,技术概念迭代速度快,算力、机房、电力是当前 AI 产业核心盈利环节;即便 AI 替代基础编码,底层网络原理、代码阅读能力仍是开发核心基础。
下篇文章预告 :深入 Socket 与文件的内核对应关系,拆解accept底层内核实现逻辑,继续讲解sk_buff网络内核结构。
9.2 全文重点总结
- 序列号核心:TCP 以报文第一个字节为起始序号,ACK = 起始序号 + 数据长度,序号随机化规避旧数据包干扰;
- 三大标志位:RST 用于连接异常复位重连,PSH 强制应用层读取缓冲区数据,URG+16 位紧急指针实现带外数据插队优先处理,紧急指针是本报文内部偏移量,标记紧急数据结束位置;
- 流量控制:通过窗口大小感知接收缓冲区容量,窗口为 0 时发送端阻塞,定时发送窗口探测包,PSH 辅助加速数据读取;
- 可靠性机制:ACK 按字节编号确认接收范围,超时重传区分数据丢包与 ACK 丢包,采用 500ms 基础单位的指数退避算法,多系统有固定重传次数阈值;
- 三次握手:第三次 ACK 无应答易引发两端认知不一致,依靠 RST 机制修复,核心作用是验证全双工、确认通信意愿;
- 连接本质 :TCP 连接是内核维护的状态结构体,
accept不参与三次握手,仅取出全连接队列连接; - 全连接队列:长度为 backlog+1,内核缓存已完成握手的连接,解耦内核与应用层,最佳实践设置 backlog 为 16/32,内核通过专用数据结构管理队列;
- 编程实践 :通过
MSG_OOB标志位收发 TCP 带外紧急数据,系统仅支持读取最后 1 字节紧急数据,多字节建议采用双 Socket 分离控制与业务数据。
结尾
uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ### 艾莉丝努力练剑 C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主 *** ** * ** *** 👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。 ❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。 ⭐ 【收藏】 把核心知识点存好,在需要时随时查、随时用。 💬 【评论】 分享你的经验或疑问,评论区一起交流避坑! 不要忘记给博主"一键四连"哦! "今日练剑达成!"
"技术之路难免有困惑,但同行的人会让前进更有方向。" |
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!
往期回顾:
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა
