举爪爪打招呼

很高兴你点开这篇文章✨

这里会持续更新我喜欢的内容,关注我,一起慢慢变好呀

👍 点赞 ⭐ 收藏 💬 评论


前言

💡 :TCP 是互联网的“可靠担当”,但它的可靠性从来不是理所当然的。这一篇我们先拆解 TCP 协议段里每个 bit 的含义,再逐一讲清它保证数据不丢、不乱、不错的三板斧:确认应答、超时重传、以及经典的三次握手与四次挥手。你还会看到“窗口”是如何让传输从“等一次确认走一步”变成“流水线作业”的。读完上篇,你就能回答:为什么 TCP 叫“面向连接”,以及它凭什么敢说自己可靠。

🐶 🐾 ✨ 🐾 🐶


一、TCP协议

1. TCP概念

  • TCP 全称为 "传输控制协议( Transmission Control Protocol "). 要对数据的传输进行⼀个详细的控制。

  • 日常编程里用到的 read 、 write 、 send 、 recv 这类IO接口,核心作用都只是 数据拷贝: 要么把应用层用缓冲区的数据,复制到内核协议栈的发送缓冲区;要么把内核接收缓冲区里的网络数据,拷贝到应用层用缓冲区供程序处理。

  • 程序只负责完成数据转交,真正管控网络传输的核心逻辑全由TCP协议自主调度:包括数据实际下发时机、单次发送数据量、传输异常重传纠错、流量控制、拥塞处理等底层传输规则,全都由TCP协议自行完成,上层应用无需干预。

  • TCP协议控制网络传输可靠性将数据从一台主机的发送缓冲区安全地交付到另一台主机的接收缓冲区 ,要求数据原封不动的发送过去,所谓发送,本质上是一种也是一种跨网络的拷贝。

由于TCP是双工的,对方也可以向我们发送,毕竟有自己的发送缓冲区和接受缓冲区!

🐾 示意图:

在这里插入图片描述


2. TCP协议段格式

在这里插入图片描述

  • 16 位源端口号:发送方主机的进程端口号,用来标识数据从哪个进程来。
  • 16 位目的端口号:目的主机的进程端口号,用来标识数据要到哪个进程去。
  • 32 位 TCP 序号:标识本报文段所发送的数据的第一个字节的编号。
  • 32 位 TCP 确认序号:接收方期望收到发送方下一个报文段的第一个字节数据的编号。
  • 4 位 TCP 首部长度:记录 TCP 头部有多少个 32 位 bit (有多少个4 字节)。所以 TCP 报头最大长度是 15 * 4 = 60 字节。
    • 由于 TCP 选项最多占 40 个字节,需要能够表示 40 字节的 TCP 选项 + 20 字节的 TCP 报头的固定长度。

🐶 🐾 ✨ 🐾 🐶


  • 6 位保留:为 TCP 将来的发展所预留的空间,目前这 6 位全部都必须为 0。
  • 6 位 TCP 标志位:用来区分 TCP 报文的类型
  • URG: 紧急指针是否有效
  • ACK: 确认号是否有效
  • PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读⾛
  • RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报⽂段
  • SYN: 请求建立连接; 我们把携带SYN标识的称为同步报⽂段
  • FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报⽂段
  • 16 位窗口大小:表示发送该 TCP 报文的发送端的接收窗口还能接收多少字节的数据流,该字段主要用于流量控制。
  • 16 位检验和:用于确认传输的数据是否损坏(发送端填充, CRC校验.,接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含TCP⾸部, 也包含TCP数据部分)
  • 16 位紧急指针:用于标识哪部分数据为紧急数据
  • 32 位 TCP 选项:长度不定 (但必须是 32 的整数倍,最多为 40 字节),内容可变,必须使用 TCP 首部的实际长度来区分 TCP 选项的具体长度。
    • TCP 选项的具体长度 = TCP 报头的实际长度 - TCP 报头的固定长度 (20 字节)。

🐶 🐾 ✨ 🐾 🐶


3. TCP的窗口

1. TCP 的发送缓冲区、接收缓冲区

  • TCP 的窗口采用缓冲区的形式实现。

  • TCP 通信双方在进行通信时,本质上是将 TCP 发送方的发送窗口 (缓冲区) 中的数据拷贝到 TCP 接收方的接收窗口 (缓冲区) 。

  • 发送缓冲区:用来暂时保存还未发送的数据。

  • 接收缓冲区:用来暂时保存接收到的数据。

  1. 应用程序向外传输数据时,调用write、send等系统调用,并非直接把数据发送到网络,只是把应用层缓存里的数据,拷贝存入TCP内核发送缓冲区,完成向传输层的数据递交。

  2. 应用程序接收网络数据时,调用read、recv等系统调用,也不是直接从网络抓取数据,只是把TCP内核接收缓冲区已存好的数据,拷贝读取到应用程序本地缓冲区。

网络通信的本质: 将发送方的发送缓冲区中的数据拷贝到接收方的接收缓冲区。


2. TCP 为什么存在缓冲区

  • 对于发送缓冲区:我们都知道,在传输数据的时候不可能存在百分百的成功机率,会存在丢包的情况,这时候就需要重新发送数据,发送缓冲区就是用来暂存未发送 / 未成功发送的数据,当数据成功发送并且被对方接收,传输数据所占用缓冲区的空间才会被其他数据使用

  • 对于接受缓冲区:收端处理数据的速度是有限的,为了不大面积丢弃没来得及处理的数据,TCP 提供了接收缓冲区用来暂存这些待处理数据

🐶 🐾 ✨ 🐾 🐶


4. TCP保证可靠性的机制

1. 确认应答(ACK)机制

确认应答(ACK)机制是用来保证可靠性的机制中最重要的机制

简单来说就是:接收方收到数据后,必须回复确认,告诉发送方“我收到了”。

🐾 它的核心逻辑如下:

  1. 发送方等待确认:每发一个数据包(如序号为 1000,含 100 字节),就会启动计时器等待 ACK。如果一直没收到,就判定丢包并重传。
  2. 接收方回复确认:收到数据后回复 ACK,告知“下一个期待的字节序号”。例如回复 ACK=1100,表示序号 1000-1099 的数据已收到,请从 1100 开始发。
  3. 累计确认:ACK 序列号是累计的。如果接收方收到 1000-1099 和 1200-1299,但中间 1100-1199 丢失,那它只会回复 ACK=1100,表示“1000-1099 收到了,但 1100 及之后都还没收到”,从而让发送方重传丢失部分。

在这里插入图片描述

在这里插入图片描述每⼀个ACK都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下⼀次你从哪⾥开始发

🐶 🐾 ✨ 🐾 🐶

1.1 序号与确认序号
  • 由于发送方没有办法区分收到的多条 ACK 分别对应的是自己曾经发送过的哪一条消息,所以就有了序号。

  • 我们都知道,TCP是面向字节流的,所以它的缓冲区就相当于是字符数组了,那序号就是数组的下标,序号是给传输的每个字节编号,初始序号是随机生成的;


1.2 序号的作用:
  • 保证数据顺序: IP包可能走不同路径乱序到达,接收方根据序号把数据重新排成原始顺序再交给应用;
  • 检测数据完整性: 接收方通过序号能发现中间是否丢了数据(比如收到了1、2、3、5,就知道4丢了),然后只确认已收到的连续数据(ACK=4),让对方重传4;
  • 去重: 如果收到先沟通序号的数据,说名是重传的重复包,直接丢弃

1.3 场景举例:
  • 发送方发数据:假设初始序号是 1000,发送 100 字节,这个报文段序号就是 1000,表示“这 100 字节从序号 1000 开始,包含的字节是1000到1099”。
  • 接收方确认:收到后回复 ACK=1100,意思是“序号 1000-1099 我都收到了,下一个请从 1100 发”。
  • 发生重传::如果主机A没收到这个ACK(ACK=1100),就会重传相同的数据。也就是重传序号仍为1000、包含字节1000-1099的报文段。

注意: 虽然序号是按照字节做的编号,但是发送的时候不是一个字节一个字节发的,而是一个数据块一个数据块地发,所以报文段的序号是初始序号

🐶 🐾 ✨ 🐾 🐶


2.超时重传机制

  • 丢包:主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据⽆法到达主机B;
  • 触发超时:如果主机A在⼀个特定时间间隔内没有收到B发来的确认应答, 就会进行重发

在这里插入图片描述

🐾 但是, 主机A未收到B发来的确认应答, 也可能是因为ACK丢失了;

在这里插入图片描述
因此主机B会收到很多重复数据. 那么TCP协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉.

🐶 🐾 ✨ 🐾 🐶


2.1 确定超时时间
  • 最理想的情况下, 找到⼀个最小的时间, 保证 “确认应答⼀定能在这个时间内返回”.

  • 但是这个时间的长短, 随着网络环境的不同, 是有差异的.

  • 如果超时时间设的太长, 会影响整体的重传效率;

  • 如果超时时间设的太短, 有可能会频繁发送重复的包;

🐾 TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间.

  • Linux中(BSD Unix和Windows也是如此), 超时以500ms为⼀个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍.
  • 如果重发⼀次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传.
  • 如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增.
  • 累计到⼀定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接

🐶 🐾 ✨ 🐾 🐶


3. 连接管理机制

重在正常情况下, TCP要经过三次握⼿建立连接, 四次挥⼿断开连接

在这里插入图片描述

🐾 服务端状态转化:

  • [CLOSED -> LISTEN] :服务器端调用listen后进入LISTEN状态, 等待客户端连接;
  • [LISTEN -> SYN_RCVD]: ⼀旦监听到连接请求(同步报⽂段), 就将该连接放入内核等待队列中, 并向客户端发送SYN确认报⽂.
  • [SYN_RCVD -> ESTABLISHED] :服务端⼀旦收到客户端的确认报⽂, 就进入ESTABLISHED状态, 可以进行读写数据了.
  • [ESTABLISHED -> CLOSE_WAIT] :当客户端主动关闭连接(调用close), 服务器会收到结束报⽂段,服务器返回确认报⽂段并进入CLOSE_WAIT;
  • [CLOSE_WAIT -> LAST_ACK] :进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据); 当服务器真正调用close关闭连接时, 会向客户端发送FIN, 此时服务器进入LAST_ACK状态, 等待最后⼀个ACK到来(这个ACK是客户端确认收到了FIN)
  • [LAST_ACK -> CLOSED] :服务器收到了对FIN的ACK, 彻底关闭连接

🐾 客端状态转化:

  • [CLOSED -> SYN_SENT] :客户端调用connect, 发送同步报⽂段;
  • [SYN_SENT -> ESTABLISHED] :connect调用成功, 则进入ESTABLISHED状态, 开始读写数据;
  • [ESTABLISHED -> FIN_WAIT_1]: 客户端主动调用close时, 向服务器发送结束报⽂段, 同时进入FIN_WAIT_1;
  • [FIN_WAIT_1 -> FIN_WAIT_2]:客户端收到服务器对结束报⽂段的确认, 则进入FIN_WAIT_2, 开始等待服务器的结束报⽂段;
    -[FIN_WAIT_2 -> TIME_WAIT]:客户端收到服务器发来的结束报⽂段, 进入TIME_WAIT, 并发出LAST_ACK;
  • [TIME_WAIT -> CLOSED]:客户端要等待⼀个2MSL(Max Segment Life, 报⽂最大⽣存时间)的时间, 才会进入CLOSED状态.

🐾 下图是TCP状态转换的⼀个汇总:

在这里插入图片描述

  • 较粗的虚线表示服务端的状态变化情况;
  • 较粗的实线表示客户端的状态变化情况;
  • CLOSED是⼀个假想的起始点, 不是真实状态;

🐶 🐾 ✨ 🐾 🐶


3.1 TCP通过三次握手建立连接

通信双方在进行 TCP 通信前,需要先建立连接(三次握手),其目的就是让对方确认各自的发送和接受能力正常,并同步初始序号

🐾 过程:

  1. 第一次握手(客端 → 服务端):客端发送 SYN 报文(SEQ=x),自身进入 SYN_SENT 状态。
    • 作用:告知服务端自己的初始序号 x,并询问“服务端,你准备好了吗?”

  1. 第二次握手(服务端 → 客端):服务端收到后,回复 SYN+ACK 报文(SEQ=y, ACK=x+1),自身进入 SYN_RCVD 状态。
    • 作用:确认收到了客端的 SYN(通过 ACK=x+1),同时发送自己的初始序号 y。相当于回答“我准备好了,你能收到我的消息吗?”

  1. 第三次握手(客端 → 服务端):客端收到后,发送 ACK 报文(SEQ=x+1, ACK=y+1),双方进入 ESTABLISHED 状态。
    • 作用:确认收到了服务端的 SYN。至此,双方确认彼此的收发能力都正常。

🐶 🐾 ✨ 🐾 🐶


🐾 为什么是三次,而不是两次?

1. 三次握手是建立双向可靠通信最少必要次数,用来快速确认网络通路完全通畅

  • 一次握手:仅证明服务端可接收客端请求,无法确认客端收发正常
  • 两次握手:仅确认客端发送正常,无法确认服务端发送能力

2. 三次握手实现双方双向建连意愿确认

  • 客端发起连接,需收到服务端应答,确认服务端同意建立通信
  • 服务端响应连接,需收到客端最终确认,完成双方双向通信授权

🐶 🐾 ✨ 🐾 🐶


3.2 TCP 通过四次挥手断开连接

操作系统维护连接也是需要成本的,因此 TCP 通信双方在通信结束之后还需要断开连接(四次挥手)

🐾 1. 第一次挥手

  • 客端发送携带 FIN 标识的报文,主动向服务端发起断开连接申请

🐾 2. 第二次挥手

  • 服务端收到断开请求后,回复 ACK 确认报文,告知客端已收到断连请求。此时连接并未立刻关闭,服务端仍可继续传输剩余未发完的数据。

🐾 3. 第三次挥手

  • 服务端所有数据全部传输完毕后,主动发送 FIN 报文,向客端提出反向断开连接请求

🐾 4. 第四次挥手

  • 客端接收服务端断连请求,回复 ACK 确认报文,告知服务端同意断开。双方双向通道全部关闭,TCP连接正式彻底断开。

文章内容太多了,分了两部分,后文在这[链接]

🐶 🐾 ✨ 🐾 🐶


举爪爪求关注

谢谢你看到这里呀

如果喜欢这篇内容,点个关注,下次更新不迷路✨

👍 点赞 ⭐ 收藏 💬 评论

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐