浅聊一下TCP协议
大家好,今天来跟大家系统梳理一下 TCP 协议的相关知识。TCP 作为传输层的核心协议,在我们日常的网络通信中扮演着至关重要的角色,不管是浏览网页、传输文件还是视频通话,背后都有 TCP 的支撑。接下来,我们就从基础概念开始,一步步深入它的核心机制。
1. 什么是 TCP 协议?
首先得搞清楚 TCP 到底是什么。TCP 的全称是传输控制协议(Transmission Control Protocol),属于 OSI 七层模型里的传输层协议,它有几个很关键的特性,咱们一个个说:
- 面向连接:就像打电话,得先接通才能说话。TCP 传输数据前,必须先建立连接,而且每条连接都是 “点对点” 的,只能一对一传输,不能一对多或者多对多。
- 基于字节流:TCP 传输数据不是按 “块” 来的,而是像水流一样的字节流。不过传输的时候,它会把这些字节流分成大小不一样的 “报文段” 再发出去。
- 核心功能:这是 TCP 的重点 —— 它能保证可靠性传输,还能做流量控制和拥塞控制,正是这些功能让 TCP 成为了可靠通信的保障。
简单理解,TCP 就像一个 “严谨的快递员”:送快递前先确认收件人在不在(建立连接),把包裹分成合适的大小(报文段),确保每个包裹都送到(可靠性),不会因为收件人收不过来而堆货(流量控制),也不会因为路上太堵而耽误送货(拥塞控制)。
2. TCP 报文段格式
咱们先看看 TCP 报文段的整体结构,它主要分两部分:TCP 首部和TCP 数据部分。而且 TCP 报文段在网络中传输时,会被包裹在 IP 报文里 ——IP 报文是 “IP 首部 + IP 数据部分”,这里的 IP 数据部分就是 TCP 报文段。
先放个 TCP 首部的格式框架(大家大概有个印象就行):
32 位字段 | 位 0-7 | 位 8-15 | 位 16-23 | 位 24-31 |
---|---|---|---|---|
第 1 行 | 源端口 | 目的端口 | ||
第 2 行 | 序号 | |||
第 3 行 | 确认号 | |||
第 4 行 | 数据偏移 | 保留 | 控制位 | 窗口 |
第 5 行 | 检验和 | 紧急指针 | ||
第 6 行及以后 | 选项(可变) | 填充 |
TCP 首部的长度不是固定的,范围在 20 字节到 60 字节之间:其中固定首部占 20 字节,剩下的 “选项” 部分最多占 40 字节,“填充” 是为了让首部长度凑够 4 字节的整数倍,没实际意义。
下面咱们把固定首部的每个字段拆解开,一个个说清楚:
2.1 源端口和目的端口(共 4 字节)
这俩很简单,就是 “sender 的端口” 和 “ receiver 的端口”,各占 2 字节(16 位)。比如你用浏览器访问网站,浏览器会用一个随机的源端口,目的端口就是网站的 80 端口(HTTP)或者 443 端口(HTTPS),这样数据才能准确找到收发双方的 “门牌号”。
2.2 序号(4 字节)
TCP 里每个字节的数据流都有自己的序号,而这个 “序号字段” 记录的是当前报文段里第一个字节的序号。比如序号是 301,当前报文段带了 100 字节数据,那下一个报文段的序号就该是 301+100=401。
这里要注意:序号不是从 0 或 1 开始的,而是在建立连接时,双方会生成一个随机的 “初始序号(ISN)”,通过 SYN 标志位传给对方,之后的序号就从 ISN 开始累加。
2.3 确认号(4 字节)
确认号是接收方发给发送方的 “反馈”,意思是 “我已经收到你之前的 data 了,你下次该从这个序号开始发”。比如接收方收到一个序号 501、数据长度 200 字节的报文段,那它期望下一个报文段的序号是 501+200=701,所以确认号就填 701。
2.4 数据偏移(4 位)
这个字段是用来告诉对方 “数据部分从哪个位置开始”,其实就是TCP 首部的长度。它的计算单位是 32 位(也就是 4 字节),比如数据偏移是 “0101”(二进制),换算成十进制是 5,那首部长度就是 5×4=20 字节;最大是 “1111”(15),首部长度就是 15×4=60 字节,这也对应了之前说的 “首部最大 60 字节”。
2.5 保留(6 位)
这部分是 “预留位”,现在暂时没用,都设为 0 就行,留着以后可能扩展功能用。
2.6 控制位(6 位)
这是 TCP 首部里的 “指挥中心”,每个位代表不同的控制功能,咱们一个个说:
- URG(紧急):当 URG=1 时,说明报文段里有紧急数据,要优先传输,不用排队。比如你在远程登录时按 “Ctrl+C” 中断程序,这个信号就是紧急数据,会触发 URG=1。
- ACK(确认):ACK 是对已收数据的确认,默认是 0;但连接建立后,所有报文段的 ACK 都得设为 1,这时确认号才有效。
- SYN(同步):专门用在建立连接的时候。如果 SYN=1、ACK=0,说明这是 “连接请求报文”;如果对方同意连接,就会回复 SYN=1、ACK=1 的报文。
- PSH(推送):当应用程序希望 “发完数据马上收到响应” 时,就会让 TCP 把 PSH 设为 1,发送方会立即发报文段,接收方收到后也会马上反馈,不用等缓存满。比如你在命令行输入一条指令,就需要 PSH 来快速响应。
- RST(复位):当连接出现严重错误(比如主机崩溃),或者收到非法报文段时,RST=1,意思是 “连接断了,得重新建”。
- FIN(终止):用来释放连接的,FIN=1 表示 “我这边数据已经发完了,咱们断开连接吧”。
2.7 窗口(2 字节)
这个字段是接收方告诉发送方 “我还有多少缓存空间能接收数据”,单位是字节。比如接收方的确认号是 701,窗口字段是 1000,意思就是 “从 701 号字节开始,我还能收 1000 字节(701~1700),你别发多了”。这样能避免发送方发得太快,接收方处理不过来导致丢包。
2.8 检验和(2 字节)
用来检查数据有没有损坏。发送方会根据数据内容算一个 “检验值”,接收方收到后也按同样的方法算一遍,如果两个值一样,说明数据没坏;不一样就说明数据丢了或者被篡改了,会丢弃这个报文段。
2.9 紧急指针(2 字节)
只有 URG=1 时这个字段才有用,它记录的是 “紧急数据有多少字节”,而且紧急数据会放在报文段数据的最前面。
3. TCP 三次握手:建立连接
TCP 是面向连接的,所以传输数据前必须先 “握手” 建立连接,而且得握三次 —— 这就是常说的 “三次握手”。咱们先把流程说清楚,再讲为什么需要三次。
3.1 三次握手的过程
首先要区分两个概念:ACK是控制位里的标志(1 位),ack是确认号字段(4 字节),别搞混了。
- 第一次握手(客户端→服务器):客户端先给服务器发一个 “同步报文”,TCP 首部里 SYN=1、ACK=0,序号 seq=x(x 是客户端的 ISN)。这一步的意思是 “服务器你好,我想跟你建连接”。
- 第二次握手(服务器→客户端):服务器收到后,知道客户端要建连接,要是同意的话,就回复一个报文,里面 SYN=1、ACK=1,序号 seq=y(y 是服务器的 ISN),确认号 ack=x+1。这一步的意思是 “我同意连接,我已经收到你 x 号开始的 data 了,你下次从 x+1 开始发”。
- 第三次握手(客户端→服务器):客户端收到服务器的确认后,再给服务器发一个报文,里面 SYN=0、ACK=1,序号 seq=x+1,确认号 ack=y+1。这一步的意思是 “我知道你同意了,我也收到你 y 号开始的 data 了,你下次从 y+1 开始发”。
举个生活化的例子:阿强想约阿珍看电影。
- 阿强说:“阿珍你好,我是阿强”(第一次握手,发起请求);
- 阿珍回复:“阿强你好”(第二次握手,同意请求);
- 阿强再说:“我有个事想跟你说”(第三次握手,确认收到同意)。
到这一步,双方都知道 “对方能收到自己的消息”,接下来就能说正事(传数据)了。
3.2 为什么需要三次握手?
最核心的原因是避免重复连接,防止资源浪费。咱们假设只有两次握手,看看会出什么问题:
比如客户端发了一个连接请求,但因为网络卡了,超时了还没收到服务器的确认,于是客户端又发了一个请求。这次网络通畅,服务器很快确认,双方开始传数据。但之前那个 “卡在路上” 的请求后来又到了服务器,服务器以为是新的连接请求,就又发了一个确认,等着客户端回复。可客户端已经在传数据了,会忽略这个确认,服务器就会一直等,白白浪费资源 —— 要是这种情况多了,服务器可能就崩了。
有了第三次握手,服务器收到客户端的第三次确认后,才知道 “客户端确实收到了我的同意”,不会再无效等待。另外,三次握手还能确认双方的初始序号(ISN),为后续的可靠性传输打基础 —— 要是只有两次,没办法可靠地同步双方的 ISN。
当然,也可以用四次、五次握手,但三次是最省资源的方式,够用就行。
3.3 三次握手的状态变化
双方在握手过程中,状态会不断变化:
- 客户端发完第一次握手后,进入SYN-SENT状态(等着服务器确认);
- 服务器收到第一次握手后,从 “LISTEN(监听)” 状态变成SYN-RCVD状态,然后发第二次握手;
- 客户端收到第二次握手后,从 SYN-SENT 变成ESTABLISHED状态(连接建立),再发第三次握手;
- 服务器收到第三次握手后,从 SYN-RCVD 变成ESTABLISHED状态。
到这一步,双方都进入 ESTABLISHED 状态,就可以开始传数据了。
4. TCP 四次挥手:释放连接
数据传完了,就得断开连接,这时候需要 “四次挥手”。为什么是四次?因为 TCP 连接是 “双向的”,要分别释放两个方向的连接。
4.1 四次挥手的过程
- 第一次挥手(客户端→服务器):客户端先发一个 “释放连接报文”,FIN=1、ACK=0,序号 seq=u(u 是客户端最后发的数据的序号)。这一步的意思是 “我这边数据发完了,要释放我到你的连接”,之后客户端就不能再给服务器发数据了。
- 第二次挥手(服务器→客户端):服务器收到后,回复一个确认报文,ACK=1、FIN=0,序号 seq=v,确认号 ack=u+1。这一步的意思是 “我知道你要断了,我已经收到你 u 号的 data 了”。到这一步,“客户端→服务器” 的连接就释放了,进入 “半关闭” 状态 —— 客户端不能发,但服务器还能给客户端发数据。
- 第三次挥手(服务器→客户端):如果服务器也没数据要给客户端发了,就发一个释放报文,FIN=1、ACK=1,序号 seq=w(w 是服务器最后发的数据的序号),确认号 ack=u+1。这一步的意思是 “我这边数据也发完了,要释放我到你的连接”。
- 第四次挥手(客户端→服务器):客户端收到后,回复一个确认报文,ACK=1、FIN=0,序号 seq=u+1,确认号 ack=w+1。这一步的意思是 “我知道你要断了,你可以关连接了”。服务器收到这个确认后,就断开连接;客户端会等一段时间(2MSL),确认服务器断了,自己再断。
简单说:先断客户端到服务器的路,再断服务器到客户端的路,因为两边可能还有数据没发完,不能一下子全断了。
5. TCP 的可靠性传输机制
TCP 最核心的优势就是 “可靠”—— 不管网络怎么波动,都能尽量保证数据不丢、不重、按序到。这背后是一整套机制,咱们逐个说:
5.1 确认应答(ACK)
最基础的机制:接收方收到数据后,必须给发送方发一个 “确认应答”(ACK)。发送方看到 ACK,就知道 “数据到了”;要是没看到,就觉得 “数据可能丢了”,会触发后续的重传。
比如发送方发了一个序号 100、长度 50 的报文段,接收方收到后,就发一个 ack=150 的确认,意思是 “100~149 的字节我都收到了,你下次从 150 开始发”。
5.2 重传机制
要是发送方没收到 ACK,就会重传数据。TCP 有三种重传方式:
5.2.1 超时重传
发送方发完数据后,会启动一个定时器(超时时间 RTO),如果在 RTO 内没收到 ACK,就认为数据丢了,重传该数据。
这里要注意两个概念:
- RTT(往返时间):数据从发送到收到 ACK 的总时间,比如发数据用了 1 秒,收 ACK 用了 1 秒,RTT 就是 2 秒。
- RTO(超时重传时间):RTO 要比 RTT 略大一点,比如 RTT 是 2 秒,RTO 可能设为 2.5 秒,避免因为网络延迟导致误判。
如果重传后还是没收到 ACK,RTO 会翻倍(比如从 2.5 秒变成 5 秒,再变成 10 秒),直到重传次数达到上限(比如 10 次),就认为网络或对方主机出问题了,强制断连接。
但超时重传有个缺点:RTO 可能比较长,比如 RTO 设为 6 秒,要等 6 秒才知道丢包,效率低。于是就有了 “快速重传”。
5.2.2 快速重传
快速重传不看时间,看 “重复的 ACK”。比如发送方发了 Seq1、Seq2、Seq3、Seq4、Seq5:
- Seq1 到了,接收方发 ack=2;
- Seq2 丢了,Seq3 到了,接收方还是发 ack=2(因为 Seq2 没收到,没法确认后续的);
- Seq4、Seq5 到了,接收方还是发 ack=2。
这时发送方连续收到 3 个相同的 ack=2,就知道 “Seq2 丢了”,不用等超时,直接重传 Seq2。这样比超时重传快多了。
5.2.3 SACK(选择性确认)
快速重传能知道 “哪个数据丢了”,但有个问题:发送方不知道 “除了丢的那个,其他数据有没有到”。比如 Seq2 丢了,但 Seq3、Seq4、Seq5 到了,发送方要是只重传 Seq2 就行,不用重传 Seq3-5。
SACK 就是解决这个问题的:它在 TCP 首部的 “选项” 字段里,加了一个 “已收数据的范围”,比如接收方告诉发送方 “我收到了 1~100、151~200 的字节”,发送方就知道 “101~150 丢了”,只重传这部分就行。
5.3 滑动窗口
如果每次发一个数据都要等 ACK,效率太低了 —— 比如 RTT 是 2 秒,每秒只能发 1 个数据,要是能连续发多个,效率就高了。滑动窗口就是干这个的。
5.3.1 什么是滑动窗口?
窗口其实就是操作系统开辟的一块 “缓存空间”,发送方可以在窗口内连续发数据,不用等每个数据的 ACK。比如窗口大小是 3,发送方可以一次发 3 个数据,等收到 ACK 后,再 “滑动” 窗口,发下一批。
举个例子:窗口大小 3,发送方发了 Seq1、Seq2、Seq3,不用等 ACK,直接发;之后收到 Seq1 的 ACK,窗口就 “向右滑 1 位”,可以发 Seq4 了;再收到 Seq2 的 ACK,窗口再滑 1 位,发 Seq5…… 这样就能连续传输,效率大大提高。
5.3.2 窗口大小由谁决定?
窗口大小是接收方决定的 —— 接收方在 ACK 报文中,通过 “窗口字段” 告诉发送方 “我还有多少缓存空间”,发送方的窗口大小不能超过这个值。比如接收方说 “窗口大小是 1000”,发送方最多只能连续发 1000 字节的数据,避免接收方处理不过来。
5.3.3 发送方的窗口划分
发送方的窗口可以分成 4 部分:
- 已发送且收到 ACK 的(比如 1~31 字节);
- 已发送但没收到 ACK 的(比如 32~45 字节);
- 未发送但在接收方缓存范围内的(比如 46~51 字节);
- 未发送且超过接收方缓存范围的(比如 52 字节以后)。
当收到 ACK 后,窗口会向右滑 —— 比如收到 32~36 字节的 ACK,窗口就滑 5 位,46~51 变成 “已发送未确认”,52~56 变成 “未发送但可发”,这样就能持续传输。
5.3.4 滑动窗口的作用
- 连续发数据,减少等待时间,提高效率;
- 基于缓存管理,没收到 ACK 的数据会存在缓存里,丢了可以重传;
- 配合 “累计确认”—— 比如收到 ack=700,就说明 700 之前的所有数据都到了,不用一个个确认,即使中间某个 ACK 丢了,也不影响。
6. 流量控制与拥塞控制
很多人会把这两个搞混,其实它们的目标不一样:流量控制是 “控制发送方和接收方之间的速度”,拥塞控制是 “控制发送方和整个网络之间的速度”。
6.1 流量控制
流量控制的目的是让发送方的速度匹配接收方的处理能力,避免接收方缓存满了丢包。
TCP 是用滑动窗口实现流量控制的:接收方在 ACK 报文中,通过 “窗口字段” 告诉发送方自己的剩余缓存空间。如果接收方处理不过来,就把窗口字段设为 0,发送方就会停止发数据,直到接收方处理完,再把窗口字段设为非 0,发送方再继续发。
比如接收方缓存满了,发一个 “窗口 = 0” 的 ACK,发送方就停了;等接收方处理了一部分数据,缓存有空间了,发一个 “窗口 = 500” 的 ACK,发送方就继续发 500 字节的数据。
6.2 拥塞控制
拥塞控制的目的是避免发送方发太多数据,导致网络拥堵。比如网络本来只能传 100 个数据,发送方一次发 1000 个,就会导致数据堵在网络里,延迟变大、丢包增多,进而触发重传,更堵 —— 这是恶性循环。
TCP 用四个算法来做拥塞控制,发送方会维护一个 “拥塞窗口(cwnd)”,cwnd 的大小会根据网络拥堵情况动态变化。
6.2.1 慢启动
刚开始发数据时,不知道网络能承受多少,所以先 “慢一点”——cwnd 初始设为 1,发 1 个报文段;收到 ACK 后,cwnd 翻倍(变成 2);再收到 ACK,再翻倍(变成 4)…… 直到 cwnd 达到一个 “慢启动门限(ssthresh)”。
比如 ssthresh 设为 16,cwnd 会从 1→2→4→8→16,到 16 的时候就进入下一个阶段。
6.2.2 拥塞避免
慢启动时 cwnd 是指数增长,太快了容易拥堵,所以到 ssthresh 后,就进入 “拥塞避免”—— 每个轮次(收到一批 ACK),cwnd 只加 1,变成线性增长。比如 cwnd=16,下一轮变成 17,再下一轮 18…… 这样增长慢一点,不容易触发拥堵。
6.2.3 拥塞发生
如果出现丢包,就说明网络拥堵了,这时候要根据 “重传方式” 调整 cwnd 和 ssthresh:
- 超时重传:说明拥堵比较严重,把 ssthresh 设为当前 cwnd 的一半,cwnd 重置为 1,重新进入慢启动。比如当前 cwnd=20,ssthresh=16,超时重传后,ssthresh=10,cwnd=1,从头开始慢启动。
- 快速重传:说明拥堵不严重(只是丢了个别数据),把 cwnd 设为当前 cwnd 的一半,ssthresh 也设为这个值,然后进入 “快速恢复”。比如当前 cwnd=20,快速重传后,cwnd=10,ssthresh=10。
6.2.4 快速恢复
快速恢复的思路是 “既然能收到 3 个重复 ACK,说明网络还能传数据,不用回到慢启动”:
- 先把 cwnd 设为 ssthresh + 3(因为收到了 3 个重复 ACK,说明有 3 个数据到了);
- 重传丢失的数据;
- 如果再收到重复 ACK,cwnd 就加 1;
- 等收到新的 ACK(确认丢失的数据已收到),就把 cwnd 设为 ssthresh,回到拥塞避免阶段。
7. 总结:TCP 如何保证可靠性?
最后咱们把 TCP 的可靠性机制汇总一下,方便大家记忆:
- 校验和:检查数据是否损坏,坏了就丢;
- 序列号:给每个字节编号,保证数据按序到达,不重复;
- 确认应答(ACK):接收方收到数据必须反馈,发送方知道数据已到;
- 重传机制:超时重传、快速重传、SACK,丢了就重发;
- 连接管理:三次握手建连接,四次挥手断连接,保证连接可靠;
- 流量控制:用滑动窗口匹配收发双方速度,避免接收方处理不过来;
- 拥塞控制:用慢启动、拥塞避免等算法,避免网络拥堵;
- 滑动窗口:连续发数据,提高效率,配合累计确认减少 ACK 开销;
- ARQ 协议:基础的 “发完等确认,确认再发下一个” 机制,保证单包可靠。
以上就是 TCP 协议的核心内容了,从基础概念到报文格式,再到连接管理和可靠性机制,希望大家看完能对 TCP 有一个清晰的认识。如果有疑问,欢迎在评论区交流~