网络原理 ——TCP 协议
TCP 报文结构
TCP 头部 20字节(无选项),关键字段:
字段 | 长度(bit) | 说明 |
---|---|---|
源端口 | 16 | 发送方端口 |
目的端口 | 16 | 接收方端口 |
序列号(seq) | 32 | 数据字节的编号 |
确认号(ack) | 32 | 期望收到的下一个字节编号(ACK=1时有效) |
数据偏移 | 4 | TCP 头部长度(单位:4字节) |
控制位 | 6 | URG 、ACK 、PSH 、RST 、SYN 、FIN |
窗口大小 | 16 | 接收方的可用缓冲区大小 |
校验和 | 16 | 数据完整性校验 |
紧急指针 | 16 | URG=1 时有效,指示紧急数据位置 |
TCP 核心特性
一、 可靠性(Reliability)
TCP 确保数据 无丢失、无重复、无错误、按序到达,主要依赖以下机制:
-
确认应答(ACK):接收方收到数据后,发送 ACK(Acknowledgment) 确认。
-
超时重传(Retransmission):如果发送方未收到 ACK,会在超时后重传数据。
-
序列号(Sequence Number):每个字节分配唯一编号,解决 乱序 和 重复 问题。
-
校验和(Checksum):检测数据是否损坏,若校验失败则丢弃。
1、确认应答
发送方和接收方之间用来确认对方是否收到信息的机制,发送应答报文的控制位的 ACK 为有效 1。如图每次发送消息都会确认是否送达。
但是在网络中是多变的,有可能会出现 “后发先至” 的情况存在。就如下图所示由于每条数据报的发送路径不一样,会造成消息的混乱,本来要给骨干涨工资的,变成说“say you”了,为了避免出现这样的情况,就引入了 “序号和确认序号” 。
2、序列号
它会给每一条信息编号,在接收方收到消息后在返回ACK时返回收到的序号,此时也避免了发送方不知道丢失的数据报,也可以根据这个编号在缓冲区进行排序。在下图中假如发送方一直没有接收到2001的应答报文,则发送方就知道1001~2000的数据丢失。然而ACK 也会丢包,发送方没有收到ACK,那怎么知道数据到没到呢?这就要涉及下一个特性 “超时重传”
3、超时重传
超时重传是对确认应答的重要补充,主要就是针对丢包的场景。发送方没有收到ACK有两种情况,一种是发送的数据包丢包,一种是接收方返回的ACK丢包。
第一种情况,在没有收到ACK超过一定时间后,就会触发超时重传机制,这是理所应当的。
第二种情况,也会触发超时重传,但是接收方已经收到一次数据,面对接收两次数据,在操作系统维护的一个 “接收缓冲区”(内存空间)中就会根据序列号进行 “去重”,以保证接收到读取到的数据是有序且不重复的。
万一重传后右没有收到应答,则会等待更长时间后继续重传,直到达到重传时间达到上限,依旧没有成功,TCP 连接就会“重置”(涉及“复位报文”控制位RST),发送方就会单方面断开连接。
二、连接管理(Connection-Oriented)
通信前必须先 建立连接,通信后 释放连接:
1、三次握手(Three-Way Handshake)
意义:
- 投⽯问路,验证通信链路是否畅通
- 验证通信双方的发送能力和接收能力
- 协商关键信息
建立连接:
- SYN(Client → Server):
SYN=1, seq=x
(同步序列号)。 - SYN+ACK(Server → Client):
SYN=1, ACK=1, seq=y, ack=x+1
。 - ACK(Client → Server):
ACK=1, seq=x+1, ack=y+1
。
Client Sserver
2、四次挥手(Four-Way Handshake)
释放连接:
- FIN(主动方 → 被动方):
FIN=1, seq=u
。 - ACK(被动方 → 主动方):
ACK=1, ack=u+1
(半关闭状态)。 - FIN(被动方 → 主动方):
FIN=1, seq=v
。 - ACK(主动方 → 被动方):
ACK=1, ack=v+1
(等待 2MSL 后关闭)。
TIME_WAIT
想⼀想,为什么是TIME_WAIT的时间是2MSL?
- MSL是TCP报⽂的最⼤⽣存时间,因此TIME_WAIT持续存在2MSL的话
- 就能保证在两个传输⽅向上的尚未被接收或迟到的报⽂段都已经消失(否则服务器⽴刻重启,可能会收到来⾃上⼀个进程的迟到的数据,但是这种数据很可能是错误的);
- 同时也是在理论上保证最后⼀个报⽂可靠到达(假设最后⼀个ACK丢失,那么服务器会再重发⼀个FIN.这时虽然客⼾端的进程不在了,但是TCP连接还在,仍然可以重发LAST_ACK);
CLOSE_WAIT
⼀般⽽⾔,对于服务器上出现⼤量的 CLOSE_WAIT 状态,原因就是服务器没有正确的关闭 socket,导致四次挥⼿没有正确完成。 这是⼀个 BUG.只需要加上对应的close即可解决问题.
值得注意的是,在四次挥手过程中,有可能只发生三次,原因是因为TCP 有延时应答机制
三、 流量控制(Fow Control)
-
滑动窗口(Sliding Window):接收方通过 窗口大小(Window Size) 告知发送方还能接收多少数据,防止发送过快导致缓冲区溢出。
-
零窗口探测(Zero Window Probe):如果接收方窗口为0,发送方会定期发送探测包,直到窗口恢复。
1、滑动窗口
窗口的大小就是一次发送数据的多少,在一组数据发过去之后,就会等待接收方回复ACK,这就从之前的一次等待一条回复变成一次等待多条回复,大大提高了效率。在这基础上,滑动窗口不是每次都要等到一组ACK接收到了才会发送下一组数据,而是会根据接收到的回复,立即发送下一条数据,如下图所示此时就像一个窗口向右滑动。
2、如果出现丢包咋办?
情况一:数据包到达,ACK 丢失
此时在滑动窗口下不用做任何处理,因为 ACK 有序列号确认规则,假如只收到 5001 的ACK 则表示 0~ 5000 的数据都已经收到。但是如果是最后一条ACK 则会触发确认应答、超时重传。
情况二:数据包丢失
B 反复向 A 索要没有收到的1001 这个数据 A 感知到 B 连续多次索要1001之后就会认为 1001 丢包触发重传 1001.
一旦 1001-2000 数据B收到了
此时B观察发现,自己的接受缓冲区里,已经有2001-7000 这些数据了接下来从 7001 索要即可~~
3、零窗口探测
接收端处理数据的速度是有限的.如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继⽽引起丢包重传等等⼀系列连锁反应.
- 接收端将⾃⼰可以接收的缓冲区⼤⼩放⼊TCP⾸部中的"窗⼝⼤⼩"字段,通过ACK端通知发送端;
- 窗⼝⼤⼩字段越⼤,说明⽹络的吞吐量越⾼;
- 接收端⼀旦发现⾃⼰的缓冲区快满了,就会将窗⼤⼩设置成⼀个更⼩的值通知给发送端;
- 发送端接受到这个窗⼝之后,就会减慢⾃⼰的发送速度;
当发送方知道缓冲区剩余大小为0时,就会暂停发送,直到得知接收方缓冲区不再满。这里的有两种方式得知,一是接收方主动通知,二是通过发送方周期性发送的一个零窗口探测包
四、 拥塞控制(Congestion Control)
防止网络过载,实现动态平衡的主要机制:
-
慢启动(Slow Start):窗口从 1 MSS(最大报文段)开始,指数增长,直到 阈值(ssthresh)。
-
拥塞避免(Congestion Avoidance):窗口 线性增长,避免过快引发丢包。
-
快速重传(Fast Retransmit):收到 3个重复ACK 时立即重传(不等待超时)。
-
快速恢复(Fast Recovery):重传后直接进入 拥塞避免,而非慢启动。
五、延时应答
六、捎带应答
七、 基于字节流(Byte Stream)
-
无消息边界:数据被视为 连续的字节流,应用层需自行解析(如 HTTP 用
Content-Length
或Transfer-Encoding: chunked
)。 -
缓冲机制:TCP 在发送端和接收端维护缓冲区,提高传输效率。
粘包问题
八、 全双工通信(Full-Duplex)
-
同一连接可 同时双向传输,双方独立维护 序列号 和 ACK。
九、异常情况
TCP vs UDP
特性 | TCP | UDP |
---|---|---|
连接方式 | 面向连接(三次握手) | 无连接 |
可靠性 | 可靠(ACK、重传) | 不可靠 |
流量控制 | 滑动窗口 | 无 |
拥塞控制 | 复杂算法(慢启动、拥塞避免等) | 无 |
数据顺序 | 保证按序到达 | 不保证 |
头部大小 | 20 字节(最小) | 8 字节 |
传输效率 | 较低(协议开销大) | 较高 |
适用场景 | HTTP、FTP、SSH、数据库 | DNS、视频流、游戏、QUIC |
总结
TCP 的核心目标是 可靠传输,通过 连接管理、ACK、重传、流量控制、拥塞控制 等机制实现。虽然效率不如 UDP,但适用于 要求数据完整性的场景。理解 TCP 对网络编程、性能优化、故障排查至关重要。
本篇文章的插图部分来自《图解 TCP/IP》