💡前端入门网络协议-TCP

92 阅读10分钟

前言

上篇文章介绍了 IP 地址的相关内容,那么电脑拿到了IP地址,我们就可以和目标计算机通信了吗?

其实,还有一个问题需要解决,就是网络通信本身是不可靠的,数据在网络中传输会遇到各种各样的问题。比如:数据包丢失数据包重复数据包乱序数据包延迟等等,这些可能是由于网络设备故障网络拥塞传输线路损坏不同的数据包可能走不同的路径到达目的地等原因造成的。

举个例子

想象你寄出 3 个快递包裹:

  • 包裹1可能丢失了
  • 包裹2和包裹3可能走了不同的路线
  • 包裹3可能比包裹2先到达
  • 某个包裹可能被寄出多次(系统重发)

前辈们遇到这些问题,他们是怎么解决的呢?他们提出了 TCP!

TCP

TCP 是基于 IP 协议的网络协议,它提供了:

  • 确认机制
  • 重传机制
  • 排序机制
  • 流量控制
  • 拥塞控制

注意:IP 协议就像邮政系统的快递服务,而 TCP 协议就像是快递的跟踪、确认和重发机制。

下面简单了解下这些机制。

TCP 的可靠性机制

确认机制(Acknowledgment

  • 接收方收到数据包后会发送确认包(ACK)给发送方
  • 就像快递签收时的回执一样
  • 例如:A 发送数据给 B,B 收到后会说"我收到了"

重传机制(Retransmission)

  • 发送方发出数据后会启动一个定时器
  • 如果在指定时间内没收到确认,就重新发送
  • 就像快递丢失后,快递公司会重新发送一个包裹
  • 例如:
    • A 发送数据给 B
    • 等待 3 秒没收到 B 的确认
    • A 就会重新发送一次

排序机制(Sequencing)

  • 每个数据包都有序号
  • 接收方可以按序号重新排列乱序的数据包
  • 就像给快递包裹标注 1/3、2/3、3/3
  • 例如:
    • 发送方:包裹1、包裹2、包裹3
    • 接收方收到:包裹2、包裹3、包裹1
    • 接收方会按序号重新排序

流量控制(Flow Control)

  • 控制发送速率,防止接收方处理不过来
  • 接收方会告诉发送方自己能处理多少数据
  • 就像快递员会根据收件人的要求控制派件速度
  • 例如:
    • B 说"我一次最多能处理 1000 字节"
    • A 就会控制发送速度,确保不超过这个限制

拥塞控制(Congestion Control)

  • 根据网络状况调整发送速率
  • 当检测到网络拥堵时会降低发送速率
  • 就像遇到交通堵塞时,快递车会改道或放慢速度
  • 例如:
    • 如果发现数据包经常丢失
    • TCP 会认为网络可能拥堵了
    • 就会降低发送速度

校验和机制(Checksum)

  • TCP 会为每个数据包计算一个校验和(类似数据的指纹)
  • 发送方:计算数据的校验和,并随数据包一起发送
  • 接收方:重新计算收到数据的校验和,与发送方的校验和比对
  • 如果两个校验和不一致,说明数据在传输过程中被损坏了
  • 接收方会丢弃这个包,不发送 ACK 确认
  • 发送方因为没收到 ACK,就会重传这个包

一个有趣的例子:

  1. 小明给小红发了一句话:"你好啊"
  2. 发送时计算校验和是 100
  3. 传输过程中数据变成了"你坏啊"
  4. 小红收到后计算校验和是 90
  5. 发现校验和不一致(90 ≠ 100)
  6. 小红不回复"收到"
  7. 小明见没收到回复,就会重发"你好啊"
  8. 小红收到后计算校验和是 100
  9. 发现校验和一致(100 = 100)
  10. 小红回复"收到"
  11. 小明收到回复后,知道数据没问题,就停止重传

这些机制互相配合,共同保证了数据传输的可靠性。就像一个完善的快递系统,不仅要把包裹送到,还要保证按顺序送达、不丢失、不积压。


完整的例子

让我们用一个生动的"网络点餐"的例子来理解TCP的各种机制:

想象小明通过APP,在一家商店点了一个"双人套餐",包含:

  • 一份牛肉面
  • 一份炒饭
  • 两杯奶茶
  • 一份甜点

👉 确认机制(ACK)

  • 小明每收到一个食物,都会在APP上点击"收到"
  • 小明:收到牛肉面了!(ACK)
  • 商家继续送下一样

👉排序机制(Sequencing)

  • 商家给每个食物标上序号:#1面、#2饭、#3奶茶A、#4奶茶B、#5甜点
  • 即使奶茶B先到,小明也知道这是第4个品项
  • 可以正确记录哪些还没送到

👉重传机制(Retransmission)

  • 如果其中一杯奶茶在路上撒了
  • 商家15分钟后,还没收到这杯奶茶的"收到"确认
  • 就会重新准备一杯送过去

👉流量控制(Flow Control)

  • 小明说:"我家餐桌只能同时放3样食物"
  • 商家就会先送3样,等小明吃掉一样,再送下一样
  • 避免食物堆积,没地方放

👉拥塞控制(Congestion Control)

  • 商家发现路上堵车了
  • 就会改道或者放慢送餐速度
  • 防止食物在路上放太久变凉

👉校验和机制(Checksum)

  • 每样食物都有"完整性检查单"
  • 比如:奶茶必须是满杯+带吸管+有封口
  • 如果发现不完整(比如漏液),就不接收,要求重送

这样,即使遇到:

  • 堵车(网络拥塞)
  • 食物撒了(数据包丢失)
  • 送错顺序(数据包乱序)
  • 重复送了(数据包重复)
  • 食物不完整(数据损坏)

TCP的各种机制都能保证小明最终完整收到他点的所有食物,而且是按照正确的顺序,保证食物的新鲜和完整。

如果数据包丢了,TCP会重发,如果数据包乱序了,TCP会排序,如果数据包重复了,TCP会丢弃,如果数据包延迟了,TCP会等待,如果数据包失真了,TCP会丢弃。

TCP对于数据包可能出现的情况,都做了预防,确保数据包的正确,可靠,有序,不丢失,不重复,不乱序,不延迟。

这下是不是就放心多了,纵使网络状态再差,数据包再乱,有TCP在,都没有问题。而HTTP就是基于TCP协议的,所以HTTP也是可靠的。

TCP的三次握手和四次挥手

握手和挥手的过程仅作了解,但如果最近有面试,这块还是好好学一学

三次握手 - 建立连接

就像两个人见面时的对话:

客户端:你好,在吗?
服务端:在的,你好!
客户端:好的,开始聊天吧

具体过程:

  1. 第一次握手
    • 客户端发送 SYN 包
    • 表示:我想和你建立连接
    • 状态:SYN_SENT(客户端)
  2. 第二次握手
    • 服务端返回 SYN + ACK 包
    • 表示:我收到了,也想和你建立连接
    • 状态:SYN_RECEIVED(服务端)
  3. 第三次握手
    • 客户端发送 ACK 包
    • 表示:好的,我们开始通信吧
    • 状态:ESTABLISHED(双方)

为什么需要三次握手?

  • 第一次:客户端确认自己能发能收
  • 第二次:服务端确认自己能发能收,客户端能发
  • 第三次:客户端确认服务端能发能收

SYN 表示这是一个连接请求的数据包,就想说“我想和你建立连接”

ACK 表示这是一个确认包,就想说“我收到了你的消息”

四次挥手 - 断开连接

就像两个人告别时的对话:

客户端:我说完了,准备走了
服务端:好的,我知道了,等我说完
服务端:我也说完了,可以走了
客户端:好的,拜拜

具体过程:

  1. 第一次挥手
    • 客户端发送 FIN 包
    • 表示:我没有数据要发了
    • 状态:FIN_WAIT_1(客户端)
  2. 第二次挥手
    • 服务端发送 ACK 包
    • 表示:好的,我知道了,但我还有话要说
    • 状态:CLOSE_WAIT(服务端)
  3. 第三次挥手
    • 服务端发送 FIN 包
    • 表示:我也说完了,可以断开了
    • 状态:LAST_ACK(服务端)
  4. 第四次挥手
    • 客户端发送 ACK 包
    • 表示:好的,拜拜
    • 状态:TIME_WAIT(客户端)

为什么需要四次挥手?

  • TCP 是全双工的,两个方向的连接要单独关闭
  • 一方关闭连接后,另一方可能还有数据要发送
  • 所以每个方向都需要一个 FIN 和 ACK

FIN表示发送完成,但是还可以接受对方的数据


TCP讲了这么多,是不是头都要炸了,没关系,我们只需要记住两点:

  • TCP 是可靠的,HTTP 是基于 TCP 的,所以 HTTP 是可靠的
  • TCP 的连接建立和断开都是由浏览器自动处理的,我们不需要关心

下面我们看看TCP在实际前端开发中,有哪些应用场景

TCP 与前端开发的关系

HTTP 请求超时处理

因为 TCP 的重传机制,我们需要合理设置请求超时时间:

// 设置请求超时
fetch('https://api.example.com/data', {
    signal: AbortSignal.timeout(5000) // 5秒超时
}).catch(error => {
    if (error.name === 'AbortError') {
        console.log('请求超时,可能是网络拥塞或服务器响应慢');
    }
});

性能优化

TCP每次建立连接,都需要三次握手,所以每次建立连接,都需要消耗1.5个RTT(Round Trip Time)。那么理解 TCP 连接建立的开销,可以帮助优化性能:

  1. 域名收敛
    • 减少不同域名的数量,复用 TCP 连接
    • 但要平衡浏览器的并发连接数限制
  2. 资源合并
    • 合并小文件减少 TCP 连接数
    • 使用雪碧图、CSS/JS 打包等技术
  3. 预连接
<link rel="preconnect" href="https://api.example.com">
  1. 长连接优化 利用 TCP 的 keep-alive 特性优化请求:
// 使用 HTTP 长连接
fetch('https://api.example.com/data', {
    headers: {
        'Connection': 'keep-alive'
    }
});

长连接什么意思:

  • 长连接就是连接建立后,不立即断开,而是保持一段时间,这样可以减少连接建立和断开的时间开销。
  • 比如你和快递员说,我需要寄快递,快递员说好的,然后你挂了电话,快递员继续接其他快递,但是你寄快递的请求还在,快递员需要处理完其他快递后,再回来处理你的快递。
  • 这就是长连接,快递员处理完其他快递后,再回来处理你的快递。

这里把快递员比喻成:处理 HTTP 请求的 TCP 长链接

上面四个角度,都是基于避免TCP连接建立的开销,来优化性能的


总结

这篇我们了解 TCP 的一些基本概念:

  1. TCP 的可靠机制
  2. TCP 的三次握手和四次挥手

并且了解完 TCP 的三次握手后,从性能优化的角度,了解了下TCP 在前端开发中的应用场景

下篇文章,我们来看看基于 TCP 的 HTTP。这是我们前端开发打交道最多的网络协议