这个问题看似基础,但真要讲清楚,得先搞明白一件事:HTTP 本身其实什么都不管。它只负责定义”请求长什么样、响应长什么样”,至于这些内容怎么从浏览器传到服务器、路上丢了怎么办、顺序乱了怎么办——这些脏活累活,HTTP 统统甩给了下面的传输层。而它选中的这个”苦力”,就是 TCP。
要理解这个选择,得先看看 TCP 到底提供了什么,以及 HTTP 到底有多依赖这些东西。
一、HTTP 天生”体弱”,离不开可靠传输
HTTP 传的是什么?网页的 HTML、图片、JSON 数据、脚本文件。这些东西有一个共同点:少一个字节都不行。
你想象一下,如果传输过程中丢了几个字节,HTML 标签少了一半、图片数据缺了一块、JSON 格式坏掉解析报错——这些都是完全没法忍受的结果,不像语音通话丢一帧还能听个大概,数据错一点就是错,没有”差不多能用”这一说。
而网络传输本身天生就不靠谱。数据要经过无数个路由器和网关,中间随时可能丢包、可能延迟到达、也可能因为走了不同的路径导致后到的包反而先到。
原始的网络传输(比如 IP 协议)只负责”尽力而为”地把数据包送出去,至于送没送到、送没送对顺序,它不保证。
TCP 的存在,就是为了在这个不靠谱的基础上,包一层”靠谱”的外壳。它做了几件关键的事:
确认与重传。 每发一段数据,接收方都要回一个确认。发送方如果迟迟等不到确认,就认定这段数据丢了,重新发一遍。
顺序重组。 数据包在网络上可能乱序到达,TCP 会给每个字节编号,接收方收到后按编号重新排好顺序,交给上层的时候已经是完整有序的数据了。
流量与拥塞控制。 TCP 会根据网络状况动态调整发送速度,网络好的时候多发一点,网络拥堵的时候主动降速,避免把网络挤爆,也避免接收方来不及处理。
这几件事凑在一起,基本上就是”数据完整到达、顺序正确、还不会把网络冲垮”。这正好补上了 HTTP 自己完全不管的那块空白——HTTP 的设计者从一开始就没打算重新造这些轮子。
二、为什么不选 UDP
既然是传输层协议,为什么不考虑更轻量的 UDP?这也是很多人会问的问题。
UDP 确实快,因为它压根不做确认、不做重传、不管顺序,发出去就不管了。这种”不负责”的特性在实时性要求极高、能接受少量丢失的场景里非常合适,比如视频通话、在线游戏——丢一帧画面,补上一帧继续播就行,没必要为了这一帧数据停下来等重传。
但网页浏览完全是另一种诉求。设想一下,如果加载网页时用 UDP,丢了一段 HTML 或者一张图数据不完整,页面要么破损、要么直接解析失败,用户看到的是一个残缺不全的页面,这是没法接受的。
HTTP 场景要的是”数据必须完整、必须准确”,而不是”数据要实时、可以丢一点”。UDP 的设计目标和 HTTP 的需求正好是反着的,所以从一开始就不在考虑范围内。
三、代价是什么
TCP 不是没有代价的,选择它就要接受它自带的一些”负担”。
最典型的就是建立连接的开销。TCP 在真正传数据之前,要先花时间跟对方”打招呼确认”——也就是常说的三次握手,双方确认彼此都能正常收发,才开始传数据。
这个过程有延迟,尤其是在网络延迟较高的环境下(比如跨国访问),这个握手时间会被明显放大。
其次是队头阻塞。TCP 要求数据必须按顺序交给上层,如果中间某一个包丢了,哪怕它后面的包已经全部到齐,也必须等这个丢的包重传成功之后,后面的数据才能一起被处理。
这在网络不稳定的时候,会让整体感觉”卡了一下”。
这也是为什么 HTTP/3 会转而基于 UDP 来实现自己的一套可靠传输机制(QUIC)——本质上还是想要 TCP 提供的那些可靠性保障,但用一种更灵活的方式去实现,绕开 TCP 自身设计上的一些历史包袱。
这从侧面印证了一件事:HTTP 从头到尾要的都不是”TCP”这三个字母本身,而是 TCP 所提供的那种可靠、有序的传输保障。谁能提供这个保障,HTTP 就可以基于谁。
写在最后
回到最初的问题,HTTP 基于 TCP,不是因为两者是绑定在一起设计的,而是因为 HTTP 需要的东西——数据完整、顺序正确、不丢包——正好是 TCP 最擅长提供的。这是一种”刚好合适”的匹配,而不是必然的捆绑。
理解这一层,再看 HTTP/3 转向基于 UDP 的 QUIC,就不会觉得奇怪了:技术选型从来不是选择某个协议本身,而是选择这个协议背后能提供的能力。
当有更好的方式能提供同样甚至更好的可靠性、同时甩掉历史包袱的时候,换个底座,也是很自然的事。