• 文章
  • 分类
  • 归档
  • 友链


    • 文章
    • 归档
    • 分类
    • 友链

    为什么TCP是可靠的

    发布于 2026-02-01 AI问答 

    在互联网的世界里,我们每天发送的每一条消息、下载的每一个文件,背后其实都有一场极其严谨的接力赛。这场赛跑的指挥官就是 TCP 协议。人们常说 TCP 是可靠的,但这个可靠到底体现在哪里?它又是如何通过那些繁琐的步骤来支撑这份信任的?


    如何理解 TCP 是可靠传输?如何理解这个“可靠”

    在计算机网络的语境下,可靠并不意味着数据一定能百分之百送到——如果物理光缆断了,谁也没办法。它的可靠本质上是一种承诺:只要网络还没彻底断开,我发出去的数据,和你收到的数据,一定是一模一样的,不多不少,不快不慢。

    想象一下你给朋友寄一套一百万字的百科全书。快递公司为了保险,不会把它装在一个巨大的包裹里,因为一旦路上洒了水,整套书都废了。TCP 的做法是把文件拆成一页一页的,并给每一页都标上页码,这就是序列号。有了页码,即便快递员把包裹送乱了顺序,朋友也能按照页码重新排好。如果朋友收到了两页同样的第 42 页,直接扔掉一张就行;如果发现第 50 页之后直接跳到了第 52 页,他立刻就知道第 51 页丢了。

    这种无差错、不丢失、不重复、且按序到达的能力,就是 TCP 所谓的可靠。


    为什么非得三次握手才可靠?为什么不能只进行两次?

    很多人对 TCP 的印象始于三次握手。但这里有个误区,握手本身并不负责传数据,它负责的是建立共识。

    想象一下你给朋友打电话。你拨通后问:喂,听得见吗?这是第一次。朋友回答:听得见,你呢?这是第二次。如果你不再说话,朋友会很困惑:他到底听没听到我说话?所以你必须回一句:我也听见了。这第三次确认,才标志着沟通渠道真正打通。

    为什么两次不行?在网络世界里,数据包会迷路。假设你发了一个请求,但它在某个路由器里堵住了,你以为丢了,又发了一个。第二个请求很快修成正果,聊完挂了。这时,第一个迷路的请求突然又撞到了服务器门上。如果是两次握手,服务器一打招呼连接就建立了,可你根本没想再聊。三次握手就像是一个防伪机制,它让双方在正式谈话前,先核对好暗号,把那些陈旧的、过期的僵尸请求全部挡在门外。

    握手的简单原理步骤:

    1. 客户端发送 SYN:你好,我的初始序号是 X,我想和你说话。
    2. 服务端回复 SYN+ACK:收到 X 了,我的初始序号是 Y,我也准备好了。
    3. 客户端发送 ACK:收到 Y 了,暗号对上了,咱们开始吧。

    大文件被分割成多个包发送的时候,TCP 是如何确保中途丢失的包能正确重发?

    这是大文件传输最核心的挑战。TCP 就像一个自带精细账本和对讲机的搬运工,它不仅搬货,还会时刻核对货单。

    当发送方把文件切成多个段发出去后,并不会立刻忘记它们,而是把它们暂时存在缓冲区。接收方每收到一个段,就会回传一个确认应答(ACK)。如果中间有个包(比如编号 1001)丢了,但后面的包(2001, 3001)都到了,接收方会连续发回好几个回执说:我想要 1001。

    发送方如果连续收到三个重复的想要 1001 的请求,它就会心领神会,不等计时器超时,立刻把那段丢失的数据重新寄出一份。这种精准补漏的机制,确保了哪怕在极其不稳定的网络环境下,大文件最终也能被完整无缺地拼凑出来。

    传输的简单原理步骤:

    1. 编号:给大文件的每个字节打上序号,按段发送。
    2. 确认:接收方收到后回复 ACK,告知下一个期望收到的序号。
    3. 重传:若发送方发现某个序号一直没被确认,或者收到重复请求,则重新发送该段。
    4. 调控:发送方根据接收方的处理能力(流量控制)和网络拥堵程度(拥塞控制)动态调整发包速度。

    介绍下四次挥手:为什么分手要四次?

    握手是三次,分手却要四次。这听起来有点累赘,但其实是 TCP 全双工特性的体现——数据可以在两个方向上同时传输。当你想要断开连接时,你只能保证我不再给你发数据了,但不能保证你不再给我发数据了。

    当你想挂电话时,

    你先说:

    1. 我说完了。这是第一次挥手。
    2. 对方回:知道了,但我可能还没讲完,你再等我一下。这是第二次挥手。
    3. 此时,你进入半关闭状态,只能听,不能说。等对方也讲完了,他会告诉你:我也讲完了,挂吧。这是第三次挥手。
    4. 你回一句:好的,再见。这是第四次挥手。

    最巧妙的是,你发完最后一句再见后,并不会立刻消失,而是会原地等待一段时间(2MSL)。这是为了防止你最后那句再见在路上弄丢了,导致对方一直等在那里不敢关机。

    那为什么不干脆叫“三次挥手”?

    但实际步骤2中,如果当服务端收到客户端的 FIN 时,正好它也没有任何数据要发送了,且应用层也立刻调用了 close() 函数,那么:

    服务端会把对客户端 FIN 的确认(ACK)和自己想断开的请求(FIN)放在同一个数据包里发出去。

    此时,流程就变成了:

    1. 第一次: 客户端 -> 发送 FIN。
    2. 第二次: 服务端 -> 发送 ACK + FIN (二三步合一)。
    3. 第三次: 客户端 -> 发送 ACK。

    既然可以三次,为什么全世界的面试题和教材都坚持说四次?

    这是因为 TCP 必须保证在所有场景下都能可靠关闭。

    TCP 是全双工的: 它必须假设服务端可能还有“没说完的话”(数据)。

    协议设计的通用性: 四次挥手是最完整、最通用的模型,它涵盖了“半关闭”状态(即一方关了,另一方还能继续发数据)。

    独立性: ACK 是由 TCP 协议栈自动触发的(表示收到包了),而 FIN 通常是由应用程序主动触发的(表示我活干完了)。这两个动作在底层并不总是同步的。

    挥手的简单原理步骤:

    1. 主动方发 FIN:我讲完了,请求关闭。
    2. 被动方发 ACK:我知道了,请稍等。
    3. 被动方发 FIN:我也讲完了,可以关了。
    4. 主动方发 ACK:收到,再见(并等待一段时间后彻底释放资源)。

    要理解客户端和服务端如何“乖乖听话”并执行复杂的 TCP 协议,我们需要揭开应用程序的表象,深入到操作系统的底层。

    代码本身其实并不懂 TCP 的握手和重传,真正的“执行官”是潜伏在操作系统内核里的 TCP 网络栈(TCP Stack)。


    客户端和服务端是如何遵循并顺利执行TPC协议

    要理解客户端和服务端如何“乖乖听话”并执行复杂的 TCP 协议,我们需要揭开应用程序的表象,深入到操作系统的底层。

    代码本身其实并不懂 TCP 的握手和重传,真正的“执行官”是潜伏在操作系统内核里的 TCP 网络栈(TCP Stack)。

    1. 核心执行者:操作系统内核(Kernel)

    当你写下一行代码(比如在 Python 里调用 requests.get() 或在 Go 里使用 net.Dial())时,你的程序只是下达了一个宏观指令。

    • 分工明确: 应用程序(如浏览器、微信)住在“用户态”,它们只负责处理业务数据(比如网页内容、聊天文字)。
    • 协议实现: 所有的 TCP 规则(握手、编号、重传、滑动窗口)都实现在操作系统内核中。
    • 为什么这么做? 这样可以确保安全性和统一性。你不需要在每个 App 里都写一遍 TCP 的逻辑,而且内核可以统一调度网卡资源,防止某个恶意程序破坏网络规则。

    2. 沟通的桥梁:Socket API(套接字)

    客户端和服务端通过一种叫 Socket 的标准接口与内核通信。你可以把它想象成插座或电话机。

    服务端的执行动作:
    1. Create (创建): 告诉内核,我需要一个 TCP 类型的插座。
    2. Bind (绑定): 告诉内核,我要监听这个特定的端口(比如 80 端口)。
    3. Listen (监听): 进入被动等待状态。此时内核会自动处理发来的 SYN 包。
    4. Accept (接受): 当内核完成三次握手后,会将连接交给程序,程序此时才正式开始处理业务。
    客户端的执行动作:
    1. Connect (连接): 这是一个关键指令。一旦调用,内核就开始自动发起“三次握手”的第一个包(SYN)。

    3. 共同的剧本:TCP 有限状态机(FSM)

    为了确保双方动作同步,TCP 协议栈内置了一套严格的“剧本”,也就是有限状态机。无论在哪种操作系统上,TCP 栈都必须遵循这些状态切换。

    • CLOSED: 起始状态。
    • SYN_SENT: 客户端发了 SYN,正在等回音。
    • LISTEN: 服务端张开耳朵在听。
    • ESTABLISHED: 双方握手成功,可以正式传数据了。
    • FIN_WAIT / CLOSE_WAIT: 挥手阶段的状态。

    为什么它们能顺利执行? 因为如果对方发来的包不符合当前状态(比如还没握手就直接发数据),内核的 TCP 栈会直接丢弃这个包或者回传一个 RST(重置)包,强行终止这种不合规的行为。

    4. 数据包里的“指挥棒”:TCP 头部标志位

    双方之所以能互相理解,是因为每一个发出的 TCP 数据包里都带有一个头部(Header)。这个头部就是指挥棒,告诉对方下一步该干什么。

    • Flags (标志位): 如 SYN(请求建立)、ACK(确认收到)、FIN(请求断开)。这些开关决定了当前包的性质。
    • Sequence Number (序列号): 告诉对方,这些数据在文件里的精确位置。
    • Window Size (窗口大小): 实时告诉对方,我现在的缓冲区还能塞下多少东西。

    可以这么理解:应用程序是老板,下达“我要传文件”的指令;内核是专业的物流主管,由它来操办握手、编号、确认和补发的所有脏活累活。


    既然 TCP 这么可靠,为什么大厂都在推行 QUIC?TCP 遇到了什么天花板?

    TCP 虽好,但它毕竟是四十年前的设计,在现代移动互联网面前显得有些老派。它的天花板主要有三个。

    首先是握手太慢。在极其追求速度的今天,TCP 加 TLS 的多次往返握手会让网页打开慢上那么几百毫秒。其次是队头阻塞,由于 TCP 执着于按序到达,如果第一个包丢了,即便后面的包全到了,你也得等着,这在看视频时会导致卡顿。最后是连接迁移问题,当你从 Wi-Fi 切换到 5G 时,IP 变了,TCP 连接就会立即断开。

    于是 QUIC 协议应运而生。它跑在 UDP 之上,却在应用层自己实现了一套更聪明的可靠性逻辑。它允许你在切换网络时不断线,也允许你在丢了一个小包的情况下继续处理其他数据。它更像是穿上了运动鞋的 TCP,继承了可靠的灵魂,但拥有了更快的双腿。

     上一篇: RIP Mr.Gao 下一篇: 红楼梦小记