一、HTTP协议概述:从0.9到3.0,一个老协议的自白

讲Socket编程,绕不开HTTP。很多人觉得HTTP就是浏览器里那个地址栏的东西,其实不然。我做了十几年网络协议,见过太多人把HTTP和TCP割裂开学,结果一到调优就抓瞎。今天咱们先把HTTP的底牌翻一翻。

1.1 HTTP协议发展史:三十年,三个版本

HTTP从诞生到现在,满打满算也就三十年。但它的变化,比很多协议一辈子都精彩。

HTTP/0.9 —— 只有GET,没有头

1991年,Tim Berners-Lee搞出了第一个版本。说白了就是个文本搬运工。你发一个GET /index.html,服务器把HTML扔回来,连个状态码都没有。我在一个古董级嵌入式设备上见过这种实现,当时差点以为代码写错了——嗯,其实人家就是这么简单。

HTTP/1.0 —— 有了头,但一次一断

1996年,RFC 1945发布。引入了Content-Type、Status-Code这些头字段。但有个致命问题:每个请求都要新建TCP连接。你想想看,一个网页有10张图片,就得建10次TCP连接。我早年做门户网站优化时,光握手开销就占了页面加载时间的40%。

HTTP/1.1 —— 长连接救场

1997年,RFC 2068。核心改进就一个:Connection: keep-alive。复用TCP连接,省掉了反复握手的开销。还加了管道化(Pipelining)、分块传输(Chunked Transfer)。不过管道化有个坑——队头阻塞。我踩过这个坑,后面细说。

HTTP/2 —— 多路复用,二进制分帧

2015年,RFC 7540。Google的SPDY协议转正。核心变化:

  • 二进制分帧,不再是纯文本
  • 多路复用,一个TCP连接跑多个请求
  • 头部压缩(HPACK)
  • 服务器推送

我曾经把一个1.1的接口迁移到2.0,首屏时间从3.2秒降到1.1秒。效果就是这么明显。

HTTP/3 —— 扔掉TCP,拥抱QUIC

2022年,RFC 9114。底层从TCP换成了QUIC(基于UDP)。为什么?因为TCP的队头阻塞在弱网环境下太要命了。HTTP/2虽然解决了请求级别的队头阻塞,但TCP丢包时,所有流都得等。HTTP/3直接换UDP,每个流独立,丢一个包不影响其他流。

一句话总结:HTTP/0.9是原始人,HTTP/1.0是工业革命,HTTP/1.1是成熟期,HTTP/2是现代化,HTTP/3是未来。

1.2 HTTP在TCP/IP协议栈中的位置

这个图我建议你刻在脑子里。很多Socket问题,追根溯源都是因为没搞清楚分层。

HTTP在TCP/IP协议栈中的位置 应用层 HTTP / HTTPS / FTP / SMTP / DNS 传输层 TCP / UDP / QUIC(HTTP/3) 网络层 IP / ICMP / ARP 链路层 以太网 / WiFi / PPP HTTP位于应用层,依赖传输层TCP(或UDP)提供可靠/不可靠传输

这张图怎么看?从上往下:

  • 应用层:HTTP在这里。它只关心“我要什么资源”、“服务器返回什么”。不关心数据怎么拆包、怎么重传。
  • 传输层:TCP在这里。HTTP/1.x和HTTP/2用TCP,HTTP/3用QUIC(基于UDP)。
  • 网络层:IP在这里。负责寻址和路由。
  • 链路层:物理传输。网线、WiFi信号。

我的习惯:调试HTTP性能问题时,先看传输层。80%的慢不是因为HTTP本身,而是TCP参数没调好。比如tcp_nodelay、tcp_cork,这两个东西能让你从应用层直接控制TCP的Nagle算法。

1.3 HTTP的核心特性:无状态、无连接

这两个特性,是HTTP设计的哲学,也是它最大的坑。

无状态(Stateless)

什么意思?服务器不记得你是谁。你发两次请求,服务器觉得是两个人。为什么这么设计?因为早期Web就是文档浏览,不需要记住用户。但后来要做购物车、登录态,就尴尬了。

解决方案?Cookie、Session、Token。我见过最离谱的做法是把用户信息全塞在URL参数里——嗯,安全审计直接挂了。

我曾经踩过的坑:一个高并发接口,每次请求都去数据库查Session。QPS一上去,数据库直接被打满。后来改成Redis + Token,问题解决。记住:无状态是HTTP的基因,但业务需要状态时,别让状态成为瓶颈。

无连接(Connectionless)

HTTP/1.0时代,每个请求都新建TCP连接,用完就断。HTTP/1.1引入了长连接,但“无连接”这个设计理念还在——它指的是协议层面不维护连接状态。连接管理交给底层TCP。

这里有个容易混淆的点:

概念 HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/3
连接方式 短连接 长连接(默认) 长连接 + 多路复用 长连接 + 多路复用
队头阻塞 无(一个请求一个连接) 有(管道化) 请求级无,TCP级有 无
连接复用 不支持 支持(Keep-Alive) 支持(多路复用) 支持(多路复用)

你想想看,无连接的设计在早期是合理的——服务器资源宝贵,不能为每个用户保持一个长连接。但现在不一样了。移动端、实时通信、WebSocket,都在挑战这个设计。

1.4 为什么Socket程序员要懂这些?

说白了,HTTP是跑在Socket上的。你写Socket代码时,本质上就是在实现HTTP的传输层。我见过太多人:

  • 用短连接做高并发,结果端口被占满
  • 不知道TCP_NODELAY,导致小包延迟200ms
  • 不理解队头阻塞,优化方向全错

核心观点:HTTP协议不是黑盒。你理解了它的发展史,就知道为什么Socket参数要那样配;你理解了它的分层位置,就知道问题出在哪一层;你理解了无状态无连接,就知道什么时候该用WebSocket、什么时候该用长轮询。

嗯,这一章就到这。后面我们会从Socket的角度,把HTTP的每个细节拆开揉碎。准备好了吗?