【每日八股】计算机网络篇(二):TCP 和 UDP

发布于:2025-03-05 ⋅ 阅读:(15) ⋅ 点赞:(0)

目录

TCP 的头部结构?

请添加图片描述

  • 源端口:16位,标识报文的返回地址。
  • 目的端口:16位,指明接收方计算机上的应用程序接口。
  • 序列号(seq):32位,在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就累加一次该数据字节数的大小。用来解决网络包乱序问题
  • 确认号(ack):32位,指下一次期望收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收用来解决丢包的问题
  • 数据偏移/首部长度:4位,TCP首部可能含有可选项内容,所以TCP报头的长度是不确定的,报头不包含任何任选字段则长度为20字节,4位首部长度字段所能表示最大长度为60字节。首部长度也叫数据偏移,因为首部长度实际上指示了数据区在报文段中的起始偏移值。
  • 保留:6位,为将来定义新的用途保留,现在一般置0。
  • 校验和:16位,由发送端填充,接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏,这个校验不仅包括TCP头部,也包括数据部分。这是TCP实现可靠传输的一个重要保障
  • 窗口:16位,是 TCP 流量控制的一个手段。通过窗口告诉对方本端的 TCP 接收缓冲区还能容纳多少字节的数据,这样对方可以控制发送数据的速度,从而达到流量控制。窗口大小为 16 bit 字段,因而窗口大小最大为 65535。
  • 紧急指针:16位,只有当 URG 标志置 1 时紧急指针才有效。紧急指针是一个正的偏移量,和序列号字段中的值相加表示紧急数据最后一个字节的序号。使用紧急指针是发送端向另一端发送紧急数据的一种方式。
  • 选项和填充:TCP头部的最后一个选项字段是可变长的可选信息。这部分最多包含40字节,因为TCP头部最长是60字节。 最常见的可选字段是最长报文大小MSS,每个连接方通常都在通信的第一个报文段中指明这个选项,它表示本端所能接受的最大报文段的长度。
  • 数据部分:TCP 报文段中的数据部分是可选的。在连接建立或者终止时,双方交换的报文段仅有 TCP 首部;如果一方没有数据要发送,也会使用没有任何数据的首部来确认收到的数据;在处理超时的许多情况中,也会发送不带任何数据的报文段。

还包括控制位:

  • URG:紧急指针标志,为1时表示紧急指针有效,该报文应该优先传送,为0则忽略紧急指针。
  • ACK:确认序号标志,为1时表示确认号有效,为0表示报文中不含确认信息。携带ACK标识的TCP报文段被称为确认报文段(注意区分控制位 ACK 和确认号 ack)。
  • RST:重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接,或者用于拒绝非法的报文段和拒绝连接请求。称携带RST标志的TCP报文段为复位报文段。
  • SYN:表示请求建立一个连接。称携带SYN标志的TCP报文段为同步报文段。
  • FIN:finish标志,用于释放连接,为1时表示发送方已经没有数据发送了,即关闭本方数据流。称携带FIN标志的TCP报文段为结束报文段。
  • PSH:push标志,为1表示是带有push标志的数据,指示接收方在接收到该报文段以后,应优先将这个报文段交给应用程序,而不是在缓冲区排队。

ACK、SYN、FIN 三个控制位常见于 TCP 三次握手建立连接和四次挥手释放连接的过程中。

TCP 如何保证可靠传输?

1. 确认应答机制

  • 接收方对每个成功接受的数据发送 ACK 包确认;
  • 序列号(seq)和确认号(ack)需要精确匹配数据段;

2. 超时重传

  • 根据动态计算的 RTT(往返时间)确定超时阈值;
  • 快速重传:收到 3 个重复的 ACK 立即重传,无需等待;

3. 数据排序与去重

  • 每个字节分配唯一的序列号;
  • 接收方缓存乱序数据,按序重组后交给应用层;
  • 通过序列号检测重复数据并丢弃;

4. 流量控制

  • 滑动窗口机制可以动态调整发送速率;
  • 接收方通过窗口大小字段告知发送方当前可用的缓冲区大小;
  • 零窗口探测(ZWP)解决窗口更新丢失问题;

5. 拥塞控制

  • 慢启动:指数增长至阈值;
  • 拥塞避免:线性增长窗口;
  • 快速恢复:通过重复 ACK 触发;

6. 校验和

  • 错误的数据将被静默丢弃,等待超时重传。

TCP 的三次握手?

第一次握手

客户端发送请求连接,将首部 SYN 标识设置为 1,初始化序列号 seq = x(x 由客户端随机生成),发送给服务器,并进入 SYN_SENT 状态,等待服务器确认。

该报文不携带应用层数据,仅包含 TCP 首部信息。

第二次握手

服务器收到 SYN = 1 的请求报文之后,发送一个确认报文,确认报文的 SYN 和 ACK 都标识为 1(归纳为 SYN-ACK),确认值 ack = x + 1,同时自己需要初始化序列号 seq = y,将报文发送给客户端之后,服务器进入 SYN_RECV 状态。

SYN-ACK 报文同时完成对客户端 SYN 的确认和自身 SYN 的发送,

第三次握手

客户端收到 SYN-ACK 后,向服务器发送 ACK 包,其中 ACK = 1,seq = x + 1,ack = y + 1,此包发送完毕之后,客户端立即进入 ESTABLISHED 状态,服务器接收到这个包之后,也进入 ESTABLISHED 状态。

TCP 为什么要三次握手?

核心原因在于确保通信双方的双向通信能力可靠同步

即,只有通过三次握手才能证明客户端和服务端的收发能力正常。

三次握手解决了网络环境中存在的几个问题:

问题一:防止历史连接干扰

当网络中存在延迟的重复 SYN 报文时(产生的原因可能是因为网络阻塞):

  1. 客户端发送 SYN {x} 之后,若旧的 SYN 报文 {x’} 到达服务器,则…
  2. 服务器相应 SYN-ACK {x’ + 1, y},之后…
  3. 客户端通过序列号与确认号对比,发现异常(x’ != x),发送 RST 终止错误连接。

故通过三次握手机制使得客户端能够验证收到的 SYN-ACK 是否对应新的请求。考虑如果只通过两次握手就建立连接的话,那么服务器发送错误的 SYN-ACK 之后,由于客户端不会进行第三次握手,那么此时服务器会通过旧的 SYN 报文再一次与客户端建立连接,这种情况不是我们希望看到的。

问题二:确保双向通信能力验证

  • 第一次握手:验证客户端的发送能力;
  • 第二次握手:验证服务器的接收 + 发送能力;
  • 第三次握手:验证客户端的接受能力;

通过三次交互可以完整确认双工通道的可用性。

问题三:资源分配同步

  1. 服务端在收到最终客户端发送的 ACK 后才分配连接资源(如缓冲区、窗口参数);
  2. 避免因未完成连接占用资源导致的 DDOS 攻击(SYN Flood,可以理解为客户端恶意地不断向服务端发送连接请求?);
  3. 协商 MSS、窗口缩放等关键参数需要完整的三次交互。

问题四:序列号可靠同步

  • 初始序列号需要通过三次握手完成双向确认;
  • 确保后续数据传输的连续性(防止报文混淆);
  • 序列号随机化策略依赖于三次交互实现安全同步(如果只有两次同步,那么服务器无法得到来自客户端的 ACK 报文);

TCP 的三次握手报文丢失怎么办?

第一次握手丢失(SYN 丢失)

如果客户端发送了 SYN 而未收到来自服务器的 SYN-ACK,则客户端触发超时重传,默认重试 5 次,间隔时间指数增长。若最终仍未得到响应,则客户端返回 ETIMEDOUT 错误。

第二次握手丢失(SYN-ACK 丢失)

服务器发送 SYN-ACK 后未收到来自客户端的 ACK。

服务器会重传 SYN-ACK,重试次数由系统参数决定。多次失败后,服务器关闭半连接(防止 SYN Flood),客户端可能触发第一次握手重传。

第三次握手丢失(ACK 丢失)

客户端发送针对服务器第二次握手发来的 SYN-ACK 的确认 ACK 后,服务器没有收到。

服务器侧:未收到 ACK 时,重传 SYN-ACK;
客户端侧:认为连接已经建立,可能直接发送数据。若服务器收到数据,会视为有效的 ACK,完成连接。

关键机制

  1. 超时重传:每次重传间隔加倍,避免网络堵塞;
  2. 半连接队列:服务器维护未完成握手的连接队列(处于 SYN_RECV 状态),队列满时拒绝新的请求;
  3. 最大重试限制:防止无限重试消耗资源。

TCP 为什么不是两次握手?

一. 避免历史连接

前面已经提到,如果客户端已经发送了 SYN‘,但是在第一次发送的过程中由于某种原因(比如网络堵塞)没有到达服务器,客户端会重发 SYN,假设 SYN 到达服务器并开始建立连接,我们假定一种最极端的情况,即 SYN 请求对应的连接已经完成建立并已经完成数据收发并断开连接,此时堵在路上的 SYN’ 才到达服务器。

在上述情况下,由于没有第三次握手的确认机制,服务器收到 SYN’ 之后回发 SYN‘-ACK,直接与客户端建立连接,由于没有三次握手,客户端收到 SYN’-ACK 之后,不会回发 ACK,导致服务器的资源浪费。

如果采用三次握手,客户端接到 SYN‘-ACK 之后,不会再回发 ACK,这样客户端和服务端就不会建立连接。

二. 同步双方初始序列号

如果只进行两次握手,那么至多只有客户端的序列号会经过 SYN-ACK 得到确认,而由于服务器收不到来自客户端的 ACK,其序列号无法得到确认。

TCP 的四次挥手?

请添加图片描述

首次挥手(FIN 报文)

主动关闭方(比如 Client)发送 FIN = 1 的控制报文,报文包含当前序列号 seq = u(u 为已发送数据的最后一个字节序号 + 1),进入 FIN_WAIT_1 状态,停止应用层数据发送。此时内核仍会处理接收缓冲区的数据。

二次挥手(ACK 确认)

被动关闭方(如 Server)收到 FIN 后,立即发送 ACK = 1 的确认报文,确认号 ack = u + 1,序列号 seq = v,发送后进入 CLOSE_WAIT 状态。

主动关闭方接收到 ACK 后,迁移到 FIN_WAIT_2 状态,仍可接受对方传输的剩余数据。

三次挥手(FIN 响应)

被动关闭方完成数据发送后,发送 FIN = 1 报文,seq = w(w 可能大于 v + 1,取决于被动关闭方发送 FIN = 1 之前向主动关闭方发送的数据量),确认号仍为 u + 1(因为二次挥手和三次挥手之间,主动关闭方并没有发送消息),被动关闭方进入 LAST_ACK 状态。

四次挥手(最终确认)

主动关闭方收到 FIN 后,立即发送 ACK = 1 报文,ack = w + 1,进入 TIME_WAIT 状态(2MSL 等待期)。启动定时器(Linux 默认 60s)。被动房接收到 ACK 后立即关闭连接。

TCP 为什么要四次挥手?

关闭连接时,主动关闭方首先发送 FIN,表明其不会再发送数据里,但是仍然可以接收数据。

被动关闭方收到 FIN 后,首先回复 ACK,此时它可能还有数据要发送给主动关闭方,待处理和发送后,被动关闭方也没有要发送的数据了,此时才发送 FIN 给主动关闭方表示同意关闭连接。

在 FIN_WAIT_2 状态下,如何处理收到的乱序 FIN 报文,TCP 连接何时进入 TIME_WAIT 状态?

【这一条 csview 上的答案与 DeepSeek 有出入,我按照 DeepSeek 上给出的答案为准进行整理】
根据 RFC 793,当处于 FIN_WAIT_2 状态时,如果收到 FIN 报文,应该发送 ACK,并进入 TIME_WAIT 状态。即使 FIN 报文乱序到达,只要主动关闭方在 FIN_WAIT_2 状态下收到 FIN 报文,则立刻进入 TIME_WAIT 状态,发送 ACK 并启动 2MSL 定时器,定时器时间到时主动关闭方进入 CLOSED 状态。被动关闭方等到 ACK 后进入 CLOSED 状态。

TCP的四次挥手的过程中丢包了怎么办?

想要更好地理解这个问题,首先需要深刻理解 TCP 四次挥手断开连接的完整过程。仍然以下图作为基准对 TCP 四次挥手的过程进行描述:
请添加图片描述
主动关闭方 A 首先发送 FIN = 1 给被动关闭方 B,之后 A 进入 FIN_WAIT_1 状态,不再发送数据,但是可以接收数据。B 收到 FIN 之后,回复 ACK,进入 CLOSED_WAIT 状态,在此期间继续传输没有传完的数据。A 收到 B 的 ACK 之后,进入 FIN_WAIT_2 状态,仍可接收数据。B 处理完数据之后,发送 FIN 给 A,B 进入 LAST_ACK。A 收到 B 的 FIN 后启动 2MSL 定时器,并进入 TIME_WAIT,2MSL 到期即进入 CLOSED,发送 ACK 给 B。B 收到最后一个来自 A 的 ACK 之后,直接进入 CLOSED 状态。

四次挥手的报传递过程可以简化为:

  1. A → B A \rightarrow B AB:FIN;
  2. A ← B A \leftarrow B AB:ACK;
  3. A ← B A \leftarrow B AB:FIN;
  4. A → B A \rightarrow B AB:ACK;

第一次挥手丢失

即 A 传给 B 的 FIN 丢失,A 会认为自己没有收到 ACK,因此触发超时重传,直到自己收到来自 B 的 ACK 或达到重传次数上限(由内核控制)。

第二次挥手丢失

实际上和第一次挥手丢失是一样的,即 B 发给 A 的 ACK 在半路丢失,A 无法收到 ACK,触发超时重传,重新发送 FIN 给 B,直到收到 ACK 或达到超时重传次数上限。

第三次挥手丢失

此时 A 已经收到了 B 的 ACK 并进入 FIN_WAIT_2 状态(A 发送 FIN 后立马进入 FIN_WAIT_1 状态,得到 B 的 ACK 后进入 FIN_WAIT_2 状态,得到 B 的 FIN 后进入 TIME_WAIT 状态),B 进一步向 A 发送数据直到所有剩下的数据全部发送完成,这时候 B 应该给 A 发送 FIN,A 收到 FIN 后将进入 TIME_WAIT 状态。

第三次挥手丢失指的是 A 没有收到 B 的 FIN,自然 A 就不会向 B 发送确认来自 B 的 FIN 的 ACK 报文。B 发送 FIN 进入 LAST_ACK 状态后,迟迟无法收到来自 A 的 ACK,会触发超时重传,重新向 A 发送 FIN 直到收到来自 A 的 ACK 进入 CLOSED 状态。

第四次挥手丢失

A 发送给 B 的确认来自 B 的 FIN 的 ACK 报文丢失。服务端没有收到 ACK 之前处于 LAST_ACK 状态。超时之后和第三次挥手丢失一样,B 会重新给 A 发送 FIN。需要注意的是,由于第四次挥手丢失,A 实际上已经收到了 B 的 FIN,并开启 2MSL 的计时器,并给 B 发送了 ACK 报文,但是 ACK 报文丢失了,超时重传后,A 重新收到来自 B 的 FIN 后,会重置 2MSL 的定时器。在等待 2MSL 之后,A 仍会断开连接。

TCP 的延迟应答与累积应答?

TCP 协议通过延迟应答与累计应答优化传输效率,二者协同工作减少网络开销并提升吞吐量。

延迟应答

核心原理:接收方收到数据后不立即回复 ACK,而是等待一段时间(默认为 200ms),将数据与 ACK 一同回复。

触发条件:当接收缓冲区仍有未读取数据时,允许延迟发送 ACK。

优势

  • 减少了 ACK 报文的数量(可能合并多个数据确认);
  • 为应用程序争取处理时间,在 ACK 报文中携带更大的窗口值;

累计应答

工作方式:ack 号表示的是该序号之前的所有字节已经确认接收;

网络优化:发送方只需要维护最大的 ack 号,未确认的最小序号触发快速重传;

网络优化:单个 ACK 包可确认多个数据段,显著降低确认报文比例;

协同优化示例:发送方连续发送 seq = 1 ~ 1000、1001 ~ 2000,接收方延迟期间处理了 2000 字节后,ACK 报文的 ack = 2001(累计确认),同时通告扩大后的接收窗口,形成传输效率的正向循环。

延迟应答与累计应答共同构成了 TCP 流量控制的基础。

TCP 会有三次挥手出现吗?

当被动关闭方在 TCP 挥手的过程中(接收到了主动关闭方的 FIN),如果没有数据要发送,并且开启了延迟应答,那么第二次和第三次挥手会合并传输,这样就出现了三次挥手。

TCP 的 MSL?

MSL 是任何报文在网络中被丢弃前的最长存活时间,这个时间是有限的,因为 TCP 是以 IP 数据报的形式在网络中传输,IP 有限制其生存的时间 TTL。RFC 793 指出 MSL 为 2 分钟,现实中一般为 1 分钟或 30 秒。

已经建立了连接,客户端突然出现了故障怎么办?

TCP 存在保活计时器,如果客户端故障,服务器不会一直等待。通常计时器设置为 2 小时,每次服务端收到客户端发送的报文,都会重置计时器,超时之后客户端会发送探测报文,每 75 s 发送一次,如果连续 10 个探测报文都没有回复,那么服务器会认为客户端发生故障,断开连接。

什么时候用长连接?什么时候用短连接?

长连接和短连接的主要区别在于 TCP 连接的保持时间。短连接每次请求都建立新的连接,完成后即关闭;长连接则需要保持打开,可以处理多个请求。

长连接的适用场景

  1. 频繁请求:客户端需要多次请求服务器(比如网页加载图片/CSS/JS),复用 TCP 连接减少握手开销;
  2. 实时通信:WebSocket、在线游戏、即时通讯等需要双向实时数据交互的场景;
  3. 服务器推送:Server-Sent Events(SSE)等需要服务端主动推送数据的场景;
  4. 移动端优化:减少频繁建连的耗电问题(需配合心跳保持连接);

短连接适用场景

  1. 低频请求:客户端请求间隔较长(大于 1 分钟),如普通 API 调用;
  2. 简单交互:传统 HTTP 请求;
  3. 高安全需求:银行交易等敏感操作,减少连接被劫持的风险;
  4. 兼容旧系统:对接仅支持 HTTP/1.0 的服务端。

TCP 的半连接队列和全连接队列

半连接队列

半连接队列也称 SYN 队列,服务端收到客户端发起的 SYN 后,内核会把该连接存储到半连接队列,并向客户端发送 SYN-ACK。

全连接队列

全连接队列也称 accept 队列,服务端收到第三次握手(客户端发来的)ACK 后,内核会把连接从半连接队列中删除,然后创建新的完全的连接,并将其添加到全连接队列,等待进程调用 accept 函数时把连接取出来

什么是 SYN 攻击?如何避免?

概念

SYN 攻击是指利用合理的服务请求来占用过多的服务资源,从而使合法用户无法得到服务的响应。如果向某个服务器端口发送大量的 SYN 报文,接收到客户端发来的 SYN 报文之后,服务端就需要为每个请求分配一个进程控制块 TCB,并返回一个 SYN-ACK 报文,并立即转为 SYN_RECV 半开连接状态,收不到对端ACK回复的服务端还会重传 SYN-ACK 报文, 系统会为此耗尽资源

避免方法

  1. Cache:系统收到一个 SYN 报文后,在一个专用 HASH 表中保存这种半连接信息,直到收到正确的回应 ACK 报文,再分配 TCB。其开销远小于 TCB 的开销。
  2. Cookie:服务器收到 SYN 请求后不立即分配资源,而是生成加密的 Cookie 作为响应。只有客户端返回了有效的 ACK 才会建立连接。
  3. 调整系统 TCP 参数:比如增大半连接队列的容量,或是减少 SYN-ACK 重试次数,或是在半连接队列满时直接拒绝新的请求。
  4. 部署防火墙:限制单个 IP 的 SYN 请求频率。
  5. 负载均衡:通过负载均衡器(比如 nginx、HAProxy)将请求分发到多台服务器上。

TIME_WAIT 的作用?过多如何解决?

TCP 常用的三个状态:ESTABLISHED 表示正在通信、TIME_WAIT 表示主动关闭、CLOSE_WAIT 表示被动关闭。

要想搞清楚 TIME_WAIT 的作用,首先需要清楚 TIME_WAIT 发生在何时。回忆之前总结的 TCP 四次挥手过程,主动关闭方发送 FIN 后进入 FIN_WAIT_1,等到被动关闭方的 ACK 后进入 FIN_WAIT_2,此时被动关闭方进入 CLOSE_WAIT,发送全部数据后,发送 FIN,被动方进入 LAST_ACK,主动方收到 FIN 后进入 TIME_WAIT,并回发 ACK,开启 2MSL 的计时,计时到点进入 CLOSED 状态,而被动方收到主动方的 ACK 后直接进入 CLOSED 状态。
因此,TIME_WAIT 的发生时间是在被动方将 FIN 发送给主动方且主动方接收到之后。

作用

实现全双工的可靠释放连接

如果主动关闭方在进入 TIME_WAIT 时发送给被动方的 ACK 丢失(即第四次挥手丢包),那么被动关闭方将会触发 TCP 超时重传(它会认为自己发送的 FIN 丢失了,因为它没有收到确认 FIN 的 ACK),由于处于 TIME_WAIT 状态下的主动关闭方仍然需要 2MSL 时间才会从 TIME_WAIT 转为 CLOSED 状态并关闭连接,重新收到 FIN 的主动方将会重置 2MSL 计时器,并重发 ACK。在 TIME_WAIT 期间,主动关闭方需要维护与被动关闭方之间的连接状态,包括对应的 IP 和端口号。如果主动关闭方不维护连接状态,而是在得到被动关闭方的 FIN 后直接进入 CLOSED 状态,那么当超时重传的 FIN 到达主动关闭方时,主动关闭方会发送 RST 包响应,被动关闭方会认为有错误发生。

使旧的数据包在网络中因过期而消失

等待 2MSL 可以使网络中残留的旧连接报文彻底消失。

避免相同的 TCP 四元组(源 IP、源端口、目的 IP、目的端口)的新连接收到延迟的旧数据,导致数据错乱。

缺点

  • 资源占用:处于 TIME_WAIT 会占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源、端口资源等;
  • 性能影响:高并发短连接场景易出现数万 TIME_WAIT,可能导致新的连接因端口不足而建立失败。

对缺点进行优化的方法

  • 修改短连接为长连接;
  • 扩大可使用端口号的范围;
  • 修改内核参数(客户端机器打开tcp_tw_reuse和tcp_timestamps选项、客户端机器打开tcp_tw_recycle和tcp_timestamps选项)、修改参数(缩小net.ipv4.tcp_max_tw_buckets)、使用组件(程序中使用 SO_LINGER);

TIME_WAIT 状态为什么需要经过 2MSL?

实际上这个问题的答案与 TIME_WAIT 的作用是相同的,即:

  1. 确保可靠关闭连接;
  2. 消除旧连接干扰;

CLOSED_WAIT 状态过多如何解决?

一直处于 CLOSED_WAIT 的原因是主动关闭方发送 FIN 后,被动关闭方没有发送 ACK。解决办法是对服务器程序进行排查。

TCP 和 UDP 的区别?

  1. TCP 是面向连接的,需要三次握手建立连接,断连时需要四次挥手;UDP 是不需要进行连接建立的;
  2. TCP 提供可靠的传输服务,可以保证传输数据无差错、不丢失、不重复;UDP 尽最大努力交付,不保证可靠;
  3. 每个 TCP 对应的是点对点连接;UDP 支持一对多、多对一、一对一、多对多等;
  4. UDP 对系统资源要求少,通讯效率高,实时性好,应用于高速传输及对实时性有要求的通信;TCP 适合可靠连接,比如付费、加密数据等;
  5. TCP 首部较长,有一定开销,没有额外字段时需要 20 字节,而 UDP 首部仅 8 字节,且固定不变;
  6. TCP 是流式传输,没有边界,但保序且可靠。UDP 是一个包一个包发送,有边界,可能丢包和乱序;
  7. TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层(即,TCP 和 UDP 处理长数据的手段不同,TCP 在传输层处理,UDP 在 IP 层出路i);
  8. 应用场景:TCP用于FTP文件传输,HTTP / HTTPS;UDP用于包总量较少的通信,如 DNS、SNMP 等,视频、音频等多媒体通信,广播通信。

粘包和拆包问题的解决办法?

概念

粘包

现象:接收端一次读取到多个数据包;
示例:发送方连续发送“A”、“B”、“C”,接收方一次收到“ABC”;
产生原因:

  • 发送方开启了 Nagle 算法(数据合并优化);
  • 接收方缓冲区积压了多个包之后统一读取;
  • 数据包小于 TCP 缓冲区大小,缓冲区被填满之后统一读取;

拆包

现象:接收端需要多次读取才能获取完整数据包;
示例:发送方发送了一个 200 字节的包,接收方分两次收到(120 + 80 字节);
产生原因:

  • 数据包大于 MSS(最大报文段长度);
  • 数据包超过接收缓冲区剩余空间;
  • 网络传输中的 IP 分片;

拓展:既然 TCP 是面向字节流传输的协议,为什么还会出现粘包和拆包现象?

TCP 协议本身并不存在“粘包”和“拆包”的概念,这些现象本质上是应用层数据边界解析问题在TCP传输特性下的表现。

TCP 流式传输本质

  • 发送端:将应用层数据视为无结构的字节管道
  • 接收端:只能保证字节顺序正确,无法感知原始消息结构。

传输层与应用层的核心矛盾

在这里插入图片描述

核心结论

TCP 的可靠字节流传输与应用层需要的结构化消息传输之间存在根本性矛盾。这就像通过水管连续注水(TCP 层),但需要识别每次倒入水杯的量(应用层消息),必须通过容器刻度(应用层协议)来界定

因此,解决粘包/拆包不是 TCP 协议的任务,而是应用层协议设计必须解决的问题,这也是为什么需要消息长度头、分隔符等边界标识的根本原因

解决办法

需要注意的是,粘包和拆包应该在应用层解决,而不应该在传输层解决,因为传输层的 TCP 是面向字节流的,仅负责流式传输。

粘包

  1. 定长消息法;
  2. 分隔符法;
  3. 长度前缀法;

拆包

  1. 缓冲区累积读取;
  2. 使用网络框架;

TCP 的 keepalive 和 HTTP 的 keepalive 的区别?

  • HTTP 的 keepalive 由应用层(用户态)实现,称为 HTTP 长连接;TCP 的 keepalive 由 TCP 层(内核态)实现,称为 TCP 保活机制。
  • HTTP keepalive 是指使用同一个 TCP 连接来发送和接收多个 HTTP 请求/应答,好处是避免了连接建立和释放的开销,只要任意一端没有明确提出断开连接,就保持 TCP 连接状态;TCP keepalive 是指建立 TCP 连接的两端一直没有数据交互达到触发 TCP 保活机制的条件,内核里的 TCP 协议栈就会发送探测报文,如果对端程序正常工作,收到探测报文之后就会回复响应,同时保活时间重置,如果对端主机崩溃没有响应或者网络原因报文不可达,连续几次探测报文之后TCP 会报告该 TCP 连接已经死亡
  • web 服务软件一般都会提供 keepalive_timeout 参数来指定 HTTP 长连接的超时时间。例如设置了 HTTP 长连接的超时时间是 60 秒,web 服务软件就会启动一个定时器,如果客户端在完成一个 HTTP 请求后,在 60 秒内都没有再发起新的请求,定时器的时间一到,就会触发回调函数来释放该连接。

IP 层会分片,为什么 TCP 层还需要 MSS 呢?

如果交给 IP 来进行分片,一个 IP 分片丢失,整个 IP 报文的所有分片都得重传。因为 IP 层本身没有超时重传机制,它由传输层的 TCP 来负责超时和重传。当某一个 IP 分片丢失后,接收方的 IP 层就无法组装成一个完整的 TCP 报文(头部 + 数据),也就无法将数据报文送到 TCP 层,所以接收方不会响应 ACK 给发送方,因为发送方迟迟收不到 ACK 确认报文,所以会触发超时重传,就会重发整个 TCP 报文(头部 + 数据)。

而如果交给 TCP 使用 MSS 来分片,TCP 将数据分成若干段,每一段都会加上 TCP/IP 头,丢失了哪部分重传丢失的那部分数据就可以了。如果由 IP 来进行分片,仅会在第一个 IP 报文前加上 TCP 头,如果其中有一部分 IP 包丢失,则无法恢复出完整的 TCP 报文,导致需要整个数据重传。