from socket() to HTTP parser
socket() 建管道、bind() 占端口、listen() 开门、accept() 接客;HTTP 请求就是按 \r\n 切字节流。理解这条链路,Web 服务器就不神秘了。
很多人以为 HTTP 是个高深协议。其实它就是一段约定:在 socket 字节流里,第一行是"方法 路径 版本",后面跟着头,空一行,再是体。仅此而已。
socket 给你的是一串无结构的字节,TCP 不保证一次 recv() 收到完整请求——可能收到半个头,也可能一次收到三个请求。所以 HTTP 解析器本质是个状态机:边收边解析,收够了就处理,没收够就等。这就是为什么 Nginx、Express 都有"请求未完整"的中间态——它们不是在等协议,是在等字节到齐。
从零写一遍这个解析器(按 CRLF 切、处理半包、处理 keep-alive 多请求复用),你会突然理解所有 Web 框架的"中间件"——它们就是切完字节、组装成请求对象后,挂在请求对象上的一串处理函数。
点下面的请求,看它从"TCP 握手"到"响应发出"各阶段耗时。注意 socket 阶段(握手+收字节)和 HTTP 阶段(解析+路由+处理)是两段不同的时间——很多人调优调错了地方。
剥掉所有框架,一个 Web 服务器就这么几步:
关键认知:第 4 步的 while 循环是单线程的——一次只能处理一个客。这就是为什么真实服务器要上多线程/多进程/IO 多路复用(epoll/select)。Nginx 用 epoll 一个线程管几万连接,靠的就是"哪个 socket 有字节了就处理哪个",而不是傻等一个连接。从单线程写起,再亲眼看到它被一个慢请求卡死,你才会真正理解为什么需要 epoll。
从时序演示能看出:一个请求的总耗时里,TCP 握手、TLS 握手、收字节、序列化响应这些"非业务"时间往往占大头。很多人调优盯着业务代码改,却忽略了:
所以"我的接口慢"的第一步不是看业务代码,是看时序图——是握手慢、收字节慢、还是处理慢。没有时序图就调优,等于蒙眼开车。这也是为什么可观测性(tracing)是现代后端的标配:它把"慢"定位到具体阶段。