【Linux网络编程】7. 传输层协议 TCP

文章目录

一、TCP 协议

TCP 全称为 "传输控制协议",人如其名,要对数据的传输进行一个详细的控制。

1、TCP 协议头

TCP 协议头固定 20 字节,加上选项最多 60 字节。

  • 源/目的端口号: 标识数据从哪个进程来、到哪个进程去

  • 32 位序号/确认号: 保证数据有序、不重复,实现可靠传输

  • 4 位首部长度: 单位为 4 字节,最大 15×4=60 字节

  • 6 位标志位: 控制连接状态

    • URG:紧急指针有效
    • ACK:确认号有效
    • PSH:提示接收方立即读取数据
    • RST:复位连接
    • SYN:请求建立连接(同步报文)
    • FIN:请求关闭连接(结束报文)
  • 16 位窗口大小: 流量控制,告知对方自己的接收缓冲区大小

  • 16 位校验和: 校验 TCP 首部 + 数据的完整性,出错直接丢弃

  • 16 位紧急指针: 标识紧急数据的位置

  • 选项(最多 40 字节): 扩展功能(如 MSS、时间戳等)

总结: TCP 头里的端口号决定 "给谁",序号 / 确认号保证 "可靠",标志位控制 "连接的建立与断开",窗口大小实现 "不被冲垮"。

2、确认应答(ACK)机制

TCP 给每个字节数据都编了号(序列号) ,接收方用 ACK 确认号告诉发送方:到哪个字节为止我都收到了,下次从这个编号的下一个字节发

序列号怎么用?

TCP 是面向字节流的,发送方会把数据按字节顺序编号:

  • 比如一次发送 1~1000 字节,这个段的序列号就是 1
  • 下一段 1001~2000 字节,序列号就是 1001

ACK 确认号的含义

接收方回复的 ACK 里,确认号 = 下一个要接收的字节编号,也就是:

  • 收到 1~1000 后,回复 ACK=1001
    表示1000 之前的字节都收到了,下次从 1001 开始发
  • 收到 1001~2000 后,回复 ACK=2001
    表示:2000 之前的字节都收到了,下次从 2001 开始发

总结: 序列号标记 "发了什么",ACK 确认号标记 "收到哪了",两者配合,TCP 才能保证数据不丢、不乱、不重复。

3、超时重传机制

超时重传是 TCP 保证可靠传输的核心机制,一句话概括就是超时没收到 ACK,就把数据重发一遍

两种丢包场景

TCP 不管哪种情况,都会触发重传:

  • 数据丢包: 发送方的数据包在路上丢了,收不到 ACK
  • ACK 丢包: 数据到了接收方,但确认应答丢了,发送方也收不到 ACK

重复数据的处理

接收方会收到重复数据,怎么办?

  • 利用序列号识别重复包,直接丢弃
  • 不影响上层应用,只保证传输层的正确性

超时时间怎么定?

TCP 会动态计算超时时间,兼顾效率和稳定性:

  • 以 500ms 为基础单位
  • 每次重传超时时间指数级递增:500ms → 1000ms → 2000ms → 4000ms...
  • 超过最大重传次数后,TCP 认为网络 / 对方异常,强制关闭连接

总结:超时重传 + 序列号去重,让 TCP 能在丢包的网络环境下,依然保证数据可靠送达。

4、连接管理机制

1)三次握手

目标:双方确认彼此收发能力正常

阶段 客户端 服务端 报文含义
1 CLOSED → SYN_SENT,发 SYN LISTEN → SYN_RCVD,收到 SYN 客户端发起连接请求
2 收到 SYN+ACK SYN+ACK 服务端同意并确认连接
3 ACK,进入 ESTABLISHED 收到 ACK,进入 ESTABLISHED 客户端最终确认,连接建立

总结: SYN → SYN+ACK → ACK,双方同步序列号,确认通信能力。

2)四次挥手

目标:双方数据都收发完毕,安全可靠关闭连接

阶段 客户端 服务端 报文含义
1 ESTABLISHED → FIN_WAIT_1,发 FIN 收到 FIN,回 ACK,进入 CLOSE_WAIT 客户端主动申请关闭
2 收到 ACK,进入 FIN_WAIT_2 CLOSE_WAIT,处理剩余业务数据 服务端确认关闭请求,暂不关闭
3 收到 FIN,回 ACK,进入 TIME_WAIT 数据处理完,发 FIN,进入 LAST_ACK 服务端数据发完,也申请关闭
4 等待 2MSL 后,进入 CLOSED 收到 ACK,进入 CLOSED 客户端最后确认,连接彻底断开

关键状态说明

  • ESTABLISHED:连接建立完成,可读写数据
  • TIME_WAIT:客户端必须等待 2MSL,防止最后一个 ACK 丢失,导致服务端无法正常关闭
  • CLOSE_WAIT:服务端收到客户端的 FIN,等待应用层调用 close()FIN

3)TIME_WAIT 状态

现象: 先启动服务端,再启动客户端,服务端退出后立刻重启,会报错。

原因:

  • 主动关闭连接的一方会进入 TIME_WAIT 状态 ,持续 2MSL 时间。
  • 服务端被 Ctrl+C 终止,是主动关闭方,端口在 TIME_WAIT 期间被占用,无法立刻重新绑定。

为什么是 2MSL?

  • 确保两个方向上的迟到报文、重传报文都消失,避免新连接收到旧数据。
  • 保证最后一个 ACK 丢失时,对方重发的 FIN 能被处理,让连接彻底关闭。

查看超时配置: cat /proc/sys/net/ipv4/tcp_fin_timeout

默认值通常为 60 秒(不同系统略有差异)。

解决 bind 失败:

bind() 之前,给 socket 设置 SO_REUSEADDR 选项,允许端口复用。

cpp 复制代码
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

4)CLOSE_WAIT 状态

现象: 客户端断开连接后,服务器端连接停留在 CLOSE_WAIT 状态,无法正常释放。

根本原因: 服务器没有调用 close() 关闭 socket,导致四次挥手无法完成。

  • 客户端主动断开连接(发 FIN),服务器收到后回复 ACK,进入 CLOSE_WAIT
  • 服务器的应用层没有调用 close(),就不会发送自己的 FIN,连接卡在 CLOSE_WAIT,无法继续挥手。

解决: 在客户端断开连接的分支里,加上 Close(),主动关闭 socket,让四次挥手走完。

总结: CLOSE_WAIT 是服务器端的 "僵尸连接",本质是代码漏了 close(),导致连接无法正常关闭。

5、滑动窗口

为什么要用滑动窗口?

为了解决 "一发一收" ACK 应答性能低的问题:

  • 窗口大小: 无需等待 ACK 就能连续发送的最大数据量(比如窗口大小 4000 字节,可一次性发 4 段)。
  • 工作逻辑: 先发满窗口内的所有数据;收到最早的 ACK 后,窗口向后滑动,继续发新数据。
  • 核心优势: 把多个段的 "等待 ACK 时间" 重叠起来,大幅提升吞吐率。

丢包后的两种处理方式

  • 情况 1:数据到了,但 ACK 丢了

    • 后续的 ACK 会自动覆盖前面丢的 ACK,发送端只要收到后续确认,就知道前面的数据也都收到了,不需要重传
  • 情况 2:数据本身丢了(快重传机制)

    1. 发送端会反复收到同一个 ACK(比如一直收到 "下一个要 1001")。
    2. 连续 3 次收到相同的 ACK,就判定对应段丢了,立刻重传该段。
    3. 接收端收到重传的数据后,会直接返回最新的 ACK(比如直接确认到 7001),因为中间的数据早就收到并缓存好了。

总结:滑动窗口通过批量发送提升效率,再用 "后续 ACK 补全" 和 "3 次重复 ACK 触发快重传" 解决丢包问题,既高效又可靠。

6、流量控制

什么是流量控制?

TCP 为了防止发送方发太快,接收方处理不过来 ,导致缓冲区溢出、丢包重传,会让接收方根据自己的处理能力,控制发送方的发送速度,这个机制就是流量控制

核心工作逻辑

  1. 接收方通知窗口: 每次 ACK 应答里,都会带上自己当前的接收缓冲区剩余大小(即窗口大小),告诉发送方 "我还能收多少数据"。
  2. 发送方按窗口发送: 发送方会根据这个窗口大小,调整自己的发送速度 ------ 窗口越大,发得越快;窗口越小,发得越慢。
  3. 窗口为 0 时的处理: 如果接收方缓冲区满了,会把窗口设为0,发送方就会暂停发送。
    • 为了避免后续接收方恢复后,窗口更新的 ACK 丢了导致双方 "僵住",发送方会定期发送窗口探测包,询问最新的窗口大小。

TCP 窗口大小的扩展

  • TCP 首部的窗口字段是 16 位,理论上最大只能表示65535字节。
  • 实际中通过 TCP 选项里的窗口扩大因子 M 解决:
    实际窗口大小 = 窗口字段的值 × 2M(即窗口字段值左移 M 位),以此支持更大的窗口,提升传输效率。

总结: 流量控制就是 "接收方说能收多少,发送方才敢发多少",靠 ACK 里的窗口大小实现,窗口为 0 时靠探测包打破僵局,再用窗口扩大因子突破 65535 字节的上限。

7、拥塞控制

什么是拥塞控制?

TCP 为了避免发送方一次性发太多数据,把网络 "挤爆",所以会根据网络的拥堵状态,动态调整发送速度 ,这个机制就是拥塞控制。

核心机制:慢启动 + 拥塞避免

  1. 慢启动(起步阶段)
  • 目标: 先少量发送数据,探测网络状态,避免一开始就 "炸网"。
  • 规则:
    • 初始拥塞窗口(cwnd)很小(比如 1 个段)。
    • 每收到 1 个 ACK,cwnd 就 + 1,窗口呈指数级增长(翻倍)。
    • 当 cwnd 超过「慢启动阈值(ssthresh)」时,退出慢启动。
  1. 拥塞避免(稳定阶段)
  • 目标: 防止窗口增长过快,进入拥堵状态。
  • 规则:
    • cwnd 超过 ssthresh 后,不再指数增长,而是每轮 RTT线性 + 1(加法增大)。
    • 窗口缓慢增长,给网络 "喘息的机会"。

网络拥堵了怎么办?(丢包后的处理)

当发生超时重传(网络严重拥堵的信号):

  1. 把慢启动阈值 ssthresh 减半(乘法减小)。
  2. 拥塞窗口 cwnd 重置为 1,重新进入慢启动阶段。

拥塞控制的核心就是:慢启动指数爬坡,拥塞避免线性试探,一旦拥堵立刻 "踩刹车",把窗口减半重走慢启动,在 "快传数据" 和 "不挤爆网络" 之间找平衡。

就像热恋的感觉: 刚开始谈恋爱(慢启动),热情指数级上升;进入稳定期(拥塞避免),慢慢升温;一旦闹矛盾(丢包 / 超时),热情直接跌到谷底,得重新从慢启动开始试探

8、延迟应答

什么是延迟应答?

接收端收到数据后,不立刻回复 ACK,而是稍微等一会儿再应答 ,以此来提高传输效率。

为什么要延迟应答?

  • 刚收到数据时,接收端的缓冲区剩余空间还不大(比如收到 500K,返回窗口 500K)。
  • 但接收端处理速度很快,等一小会儿数据就被消费掉了,缓冲区又空出来了。
  • 延迟应答后,返回的窗口会更大(比如直接返回 1M),窗口越大,网络吞吐量和传输效率就越高。

延迟应答的限制(不能无限等)

  • 数量限制: 一般每收到 2 个数据包,就必须应答一次。
  • 时间限制: 延迟时间不能超过固定值(通常是 200ms),超时必须应答。

总结:延迟应答就是 "攒一会儿再确认",用时间换更大的窗口,提高传输效率,同时用数量和时间限制避免 ACK 超时。

9、捎带应答

什么是捎带应答?

在延迟应答的基础上,当接收方(比如服务器)本身就要给发送方(比如客户端)回应用数据时,会把ACK 确认应答直接 "搭顺风车",跟服务器的响应数据一起发给客户端,不用单独发一次 ACK 包。

核心优势

  • 减少了一次独立 ACK 包的发送,降低了网络开销和传输次数。
  • 既实现了确认应答,又不额外占用带宽,效率拉满。

举例

就像图里的交互流程:

  • 客户端发命令(如EHLO),服务器既要回250响应,又要确认收到数据。
  • 捎带应答会把 "确认收到数据的 ACK" 和250响应合并在同一个包里发回去,不用分开两次发。

总结: 捎带应答就是 "把 ACK 和要回的数据打包一起发",省掉单独的 ACK 包,提升传输效率。

二、TCP 补充

1、面向字节流

内核缓冲区

创建 TCP Socket 时,内核会同时开辟两个缓冲区:

  • 发送缓冲区: 你调用write写的数据,先进入这里,再由 TCP 协议决定什么时候发、怎么发。
  • 接收缓冲区: 收到的数据先存在这里,你调用read时再从缓冲区取数据。

发送与接收的特点

  • 发送端(write)
    • 数据先写入发送缓冲区,不是立刻发出去。
    • 太长的数据会被拆分成多个 TCP 段发送;太短的数据会先攒着,凑够合适长度再发。
  • 接收端(read)
    • 数据先到达接收缓冲区,应用程序再从这里读取。
    • 读取方式和发送方式完全解耦,不用一一匹配。

关键特性

  • 全双工: 同一个 TCP 连接,既可以读数据,也可以写数据(因为同时存在发送和接收两个缓冲区)。
  • 字节流特性: 你怎么写、写几次,和对方怎么读、读几次,没有强制对应关系。比如:
    • 你写 100 字节:可以一次写 100 字节,也可以分 100 次每次写 1 字节。
    • 对方读 100 字节:可以一次读 100 字节,也可以分 100 次每次读 1 字节。

总结: TCP 面向字节流的本质,就是靠内核的发送 / 接收缓冲区,实现 "读写解耦、按需打包",同时支持全双工通信。

2、粘包问题

什么是粘包?

TCP 是面向字节流,没有消息边界,多个应用层数据包连在一起,应用层分不清从哪里拆分,这就是粘包。

为什么只有 TCP 有?

  • TCP:传输层拆包 / 组包,应用层只看到连续字节流,无边界
  • UDP:一个报文一个报文交付,自带边界,没有粘包问题

怎么解决?(核心:定边界)

  1. 定长协议: 所有包长度一样,按固定长度读
  2. 包头带长度: 包前加长度字段,先读长度再读数据
  3. 分隔符协议: 用特殊符号分隔包(不与内容冲突)

总结: TCP 字节流无边界导致粘包,UDP 报文有边界不会粘包;解决方法就是给应用层包加明确边界。

3、TCP 异常情况

1. 进程终止 / 机器重启

  • 系统会释放文件描述符,正常发送 FIN 报文,按标准流程关闭连接,和正常关闭无本质区别。

2. 机器掉电 / 网线断开

  • 对方无法发送 FIN 报文,接收端暂时认为连接仍存在。
    • 接收端尝试写入数据时,会收到 RST 报文,直接感知连接断开。
    • 即使无写入,TCP 的保活定时器会定期探测对方状态,超时无响应则释放连接。
  • 应用层也常做额外检测(如 HTTP 长连接、QQ 的断线重连机制),保障可靠性。

4、TCP 小结

TCP 之所以复杂,就是为了同时实现可靠传输高性能传输两大目标。

保障可靠性的核心机制

  1. 校验和: 检查数据是否在传输中损坏
  2. 序列号: 保证数据按序到达,解决乱序问题
  3. 确认应答(ACK): 让发送方知道数据已被接收
  4. 超时重传: 丢包后自动重试,保证数据不丢失
  5. 连接管理: 三次握手建立、四次挥手关闭,避免无效连接
  6. 流量控制: 接收方控制发送方速度,防止缓冲区溢出
  7. 拥塞控制: 根据网络状态动态调整发送速度,避免网络拥堵

提升传输性能的核心机制

  1. 滑动窗口: 批量发送数据,减少等待 ACK 的时间
  2. 快速重传: 收到 3 次重复 ACK 直接重传,不用等超时
  3. 延迟应答: 等数据处理完再回复 ACK,放大窗口提升吞吐
  4. 捎带应答: 把 ACK 和应用层响应一起发,减少额外报文开销

其他关键机制

各种定时器:超时重传定时器、保活定时器、TIME_WAIT 定时器等,处理异常和连接状态维护。

5、基于 TCP 的应用层协议

协议 核心用途 默认端口 关键特点
HTTP 网页浏览、数据传输 80 明文传输,无加密,是 Web 的基础协议
HTTPS 加密的网页浏览 443 在 HTTP 基础上增加了 TLS/SSL 加密,保证数据安全
SSH 远程登录、安全命令行 22 加密的远程管理协议,替代不安全的 Telnet
Telnet 早期远程登录 23 明文传输,无加密,现已基本被 SSH 淘汰
FTP 文件传输 21 (控制)、20 (数据) 双端口设计,分别处理命令和数据传输
SMTP 发送邮件 25 用于邮件客户端向服务器发送邮件,或服务器间转发邮件

自己写的 TCP 程序,本质也是自定义的应用层协议,只要双方约定好数据格式、边界规则,就能实现通信。

6、TCP/UDP 对比

不能简单说谁更好,按需选用。

TCP

  • 可靠、面向连接、有流量 / 拥塞控制,牺牲速度换可靠有序
  • 适用:文件传输、网页、重要数据交互等必须不丢包的场景。

UDP

  • 无连接、不可靠、开销小、速度快、延迟低,不保证可靠但实时性强
  • 适用:直播视频、语音通话、游戏、QQ 这类看重实时性 场景;还支持广播、多播

总结:TCP 保可靠,UDP 保实时;根据业务需求选择使用即可。

7、UDP 实现可靠传输

UDP 本身无连接、不可靠,要在应用层复刻 TCP 可靠机制:

  1. 加序列号: 保证数据有序、去重
  2. 加 ACK 确认应答: 确认对方已收到数据
  3. 加超时重传: 超时没收到 ACK 就重发
  4. 还可自行实现:滑动窗口、流量控制、拥塞控制、丢包重传等

总结:UDP 做传输层载体,应用层自己仿写 TCP 全套可靠逻辑

相关推荐
SurpriseDPD2 小时前
Linux 内核 static_branch_likely:零开销条件分支
linux
li1670902702 小时前
第2课:Linux基础指令(上)
linux·运维·服务器
li1670902702 小时前
第1课:Linux环境部署
linux·运维·服务器·vim
tian_jiangnan2 小时前
Proxmox VE – 修复 LVM Thin Pool “pve/data” 激活失败
linux·服务器·centos
程序员JerrySUN2 小时前
Jetson边缘嵌入式实战课程第三讲:L4T 与 Jetson 系统架构
linux·服务器·人工智能·安全·unity·系统架构·游戏引擎
云安全助手2 小时前
如何防范DDoS攻击呢?
运维·服务器·网络
EasyGBS2 小时前
智慧工地、明厨亮灶、平安校园……国标GB28181视频平台EasyGBS凭什么成为ToB视频方案的“万能基座”?
网络·音视频
从0开始学测试2 小时前
网络流量生成与分析工具实战
网络
鹏大师运维3 小时前
统信UOS CVE-2026-31431漏洞怎么修?先看漏洞,再看3种修复方法
linux·内核·deb·漏洞修复·统信uos·补丁·本地提权
feng_you_ying_li3 小时前
liunx之软硬链接与库的制作原理(1)
linux