协议分帧、HTTP 基础与服务加固 / Protocol Framing, HTTP Basics, and Service Hardening
1. 为什么 TCP 之后必须讲协议
TCP 把字节可靠有序地送到对端,但你的程序真正处理的是请求、响应、命令、事件或文件块。这些“业务单位”都不是 TCP 天生提供的,需要应用协议定义。
TCP stream:
48 45 4c 4c 4f 0a 57 4f 52 4c 44 ...
application protocol decides:
where a message starts
where it ends
how to parse fields
how to reject invalid input2. 分帧是什么
framing 就是从字节流里切出完整消息。没有分帧,你就无法知道 recv 到的 200 字节是半条请求、一条请求、还是三条请求连在一起。
常见分帧方式:
- 固定长度:每条消息永远 N 字节;
- 分隔符:例如行协议用
\n; - 长度前缀:先读固定大小长度,再读 payload;
- TLV:type-length-value;
- HTTP:请求行 + header + 空行 + optional body。
3. 为什么不能直接读取到字符串就 parse
网络输入不可信。你不知道对端会不会慢慢发、发超长 header、声明 4GB body、发非法 UTF-8、断在中间,或者故意构造让 parser 进入高 CPU 状态的输入。
read bytes
-> append to bounded buffer
-> parser state machine
-> complete frame?
yes: validate and dispatch
no : wait for more bytes
-> too large or timeout?
close with error4. 长度前缀协议示例
[4-byte big-endian length][payload bytes]
00 00 00 05 68 65 6c 6c 6f
length = 5
payload = hello解析规则必须包含最大长度。读取长度后先检查上限,再分配或等待 payload。
if length > MAX_FRAME_SIZE:
reject connection
else:
wait until buffer has 4 + length bytes
extract one frame5. HTTP/1.1 基础
HTTP/1.1 是文本协议,但不是“随便按字符串 split”那么简单。一个请求大致由请求行、headers、空行和可选 body 组成。
GET /index.html HTTP/1.1\r\n
Host: example.com\r\n
Connection: close\r\n
\r\nPOST 带 body 时,常见方式是 Content-Length 指明 body 字节数;chunked transfer 又是另一套分帧。一个基础服务可以先只支持 Content-Length,并明确拒绝 chunked。
6. HTTP 解析状态机
ReadingRequestLine
-> ReadingHeaders
-> ReadingBody or Complete
-> Handling
-> WritingResponse
-> KeepAlive or Closing状态机比一次性 split 更稳,因为它能处理半包、超时和大小限制。每个状态都知道自己还需要多少字节、最大允许多少字节、失败时返回什么错误。
7. 基础限制
一个网络服务至少要有这些限制:
- request line 最大长度;
- header 总大小上限;
- header 数量上限;
- body 最大长度;
- 读取 request line/header/body 的 deadline;
- 每连接 output buffer 上限;
- 全局连接数和全局内存上限。
没有限制的 parser 不是友好,而是把进程资源交给任意客户端。
8. Keep-Alive
HTTP/1.1 默认可复用连接,但复用连接意味着同一个 TCP 字节流上可能连续出现多个请求。服务端必须在响应后回到 ReadingRequestLine,而不是直接关闭或假设连接结束。
request #1
response #1
request #2
response #2
close or idle timeout如果你暂时不支持 keep-alive,就在响应里写 Connection: close 并关闭连接。不要半支持。
9. 慢连接和 Slowloris
慢连接攻击会非常慢地发送 header,占住连接和内存。解决方法不是“多开线程”,而是 header deadline、最小读取速率、header size limit 和连接数限制。
10. 路径和文件服务安全
如果 HTTP 服务读取文件,路径必须规范化并限制在根目录下。/../../etc/passwd、URL 编码、重复斜杠、符号链接都可能绕过简单字符串判断。
url path
-> percent decode carefully
-> normalize
-> join with document root
-> verify result still under root11. 响应生成
响应也有协议边界:状态行、headers、空行、body。Content-Length 必须和 body 字节数一致;错误响应也要能被客户端解析。
HTTP/1.1 200 OK\r\n
Content-Type: text/plain\r\n
Content-Length: 5\r\n
Connection: close\r\n
\r\n
hello12. 不要自己写完整 HTTP 服务器库
学习时可以写最小 parser,生产中通常应使用成熟库。HTTP 有很多边界:chunked、pipeline、代理头、TLS、压缩、升级协议、大小写、非法空白、请求走私等。自己写完整实现很容易漏安全问题。
这不矛盾:学习要懂原理,生产要尊重复杂度。
13. 协议日志
协议错误日志应包含:连接 id、peer address、当前 parser state、已读字节、限制值、错误类型、关闭原因。不要记录过大的原始 body,避免日志放大和敏感信息泄漏。
14. fuzz 和回归样本
parser 很适合 fuzz。保存触发 bug 的原始字节作为 corpus,之后每次修改 parser 都跑回归。
valid request
header too large
body length too large
missing CRLF
invalid method
partial request
two requests in one buffer15. 常见错误
- 把 TCP recv buffer 直接当成完整 HTTP 请求。
- 没有 Content-Length 上限。
- header 读取没有 deadline。
- 错误响应没有关闭或排空策略。
- 路径规范化只做字符串替换。
- parser 抛异常穿过 event loop,导致连接状态泄漏。
- keep-alive 半支持,响应后状态没有重置。
16. 练习
- 写一个长度前缀 parser,支持一次 buffer 里解析多条消息。
- 写 HTTP request line + header parser,只支持 GET 和 Content-Length。
- 构造 header 分两次到达,确认 parser 状态正确。
- 构造超大 Content-Length,确认不会分配大内存。
- 用 fuzz 或随机输入喂 parser,保存崩溃样本。
17. 总结
协议层是 TCP 字节流和业务语义之间的桥。正确的网络服务不是“能读到字符串”,而是能在半包、粘包、恶意长度、慢连接、超时、关闭和错误响应下维持明确状态。
深化切片 1:最小实验
围绕 framing 和 parser state 设计一个小实验。把一个复杂网络现象拆成一个客户端、一个服务端和一条可重复命令。先确认基础行为,再加并发、超时和异常输入。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 2:抓包观察
围绕 parser state 和 Content-Length 设计一个小实验。用 tcpdump 或 Wireshark 保存原始报文,确认看到的是 SYN、ACK、FIN、RST、payload 还是应用层解析错误。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 3:错误路径
围绕 Content-Length 和 header limit 设计一个小实验。每个系统调用都要处理 EINTR、EAGAIN、ECONNRESET、ETIMEDOUT、EPIPE 等错误,并保留上下文。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 4:资源上限
围绕 header limit 和 body limit 设计一个小实验。连接数、fd 数、缓冲区、请求大小、队列长度和超时都是协议设计的一部分。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 5:关闭顺序
围绕 body limit 和 timeout 设计一个小实验。网络程序要区分停止接收新连接、排空已有连接、发送剩余响应、关闭写方向、释放 fd。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 6:跨平台
围绕 timeout 和 keep-alive 设计一个小实验。Linux 的 epoll、BSD/macOS 的 kqueue、Windows 的 IOCP 模型不同,C++ 抽象应隐藏平台差异但暴露语义差异。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 7:生产诊断
围绕 keep-alive 和 fuzz 设计一个小实验。日志要包含 connection id、peer address、状态、读写字节数、错误码和关闭原因。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 8:安全边界
围绕 fuzz 和 framing 设计一个小实验。任何来自网络的长度、头字段、路径、编码和压缩数据都不可信,必须先限制再分配。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 9:最小实验
围绕 framing 和 parser state 设计一个小实验。把一个复杂网络现象拆成一个客户端、一个服务端和一条可重复命令。先确认基础行为,再加并发、超时和异常输入。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 10:抓包观察
围绕 parser state 和 Content-Length 设计一个小实验。用 tcpdump 或 Wireshark 保存原始报文,确认看到的是 SYN、ACK、FIN、RST、payload 还是应用层解析错误。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 11:错误路径
围绕 Content-Length 和 header limit 设计一个小实验。每个系统调用都要处理 EINTR、EAGAIN、ECONNRESET、ETIMEDOUT、EPIPE 等错误,并保留上下文。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 12:资源上限
围绕 header limit 和 body limit 设计一个小实验。连接数、fd 数、缓冲区、请求大小、队列长度和超时都是协议设计的一部分。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 13:关闭顺序
围绕 body limit 和 timeout 设计一个小实验。网络程序要区分停止接收新连接、排空已有连接、发送剩余响应、关闭写方向、释放 fd。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 14:跨平台
围绕 timeout 和 keep-alive 设计一个小实验。Linux 的 epoll、BSD/macOS 的 kqueue、Windows 的 IOCP 模型不同,C++ 抽象应隐藏平台差异但暴露语义差异。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 15:生产诊断
围绕 keep-alive 和 fuzz 设计一个小实验。日志要包含 connection id、peer address、状态、读写字节数、错误码和关闭原因。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 16:安全边界
围绕 fuzz 和 framing 设计一个小实验。任何来自网络的长度、头字段、路径、编码和压缩数据都不可信,必须先限制再分配。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 17:最小实验
围绕 framing 和 parser state 设计一个小实验。把一个复杂网络现象拆成一个客户端、一个服务端和一条可重复命令。先确认基础行为,再加并发、超时和异常输入。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 18:抓包观察
围绕 parser state 和 Content-Length 设计一个小实验。用 tcpdump 或 Wireshark 保存原始报文,确认看到的是 SYN、ACK、FIN、RST、payload 还是应用层解析错误。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 19:错误路径
围绕 Content-Length 和 header limit 设计一个小实验。每个系统调用都要处理 EINTR、EAGAIN、ECONNRESET、ETIMEDOUT、EPIPE 等错误,并保留上下文。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 20:资源上限
围绕 header limit 和 body limit 设计一个小实验。连接数、fd 数、缓冲区、请求大小、队列长度和超时都是协议设计的一部分。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 21:关闭顺序
围绕 body limit 和 timeout 设计一个小实验。网络程序要区分停止接收新连接、排空已有连接、发送剩余响应、关闭写方向、释放 fd。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 22:跨平台
围绕 timeout 和 keep-alive 设计一个小实验。Linux 的 epoll、BSD/macOS 的 kqueue、Windows 的 IOCP 模型不同,C++ 抽象应隐藏平台差异但暴露语义差异。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 23:生产诊断
围绕 keep-alive 和 fuzz 设计一个小实验。日志要包含 connection id、peer address、状态、读写字节数、错误码和关闭原因。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 24:安全边界
围绕 fuzz 和 framing 设计一个小实验。任何来自网络的长度、头字段、路径、编码和压缩数据都不可信,必须先限制再分配。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 25:最小实验
围绕 framing 和 parser state 设计一个小实验。把一个复杂网络现象拆成一个客户端、一个服务端和一条可重复命令。先确认基础行为,再加并发、超时和异常输入。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 26:抓包观察
围绕 parser state 和 Content-Length 设计一个小实验。用 tcpdump 或 Wireshark 保存原始报文,确认看到的是 SYN、ACK、FIN、RST、payload 还是应用层解析错误。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 27:错误路径
围绕 Content-Length 和 header limit 设计一个小实验。每个系统调用都要处理 EINTR、EAGAIN、ECONNRESET、ETIMEDOUT、EPIPE 等错误,并保留上下文。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 28:资源上限
围绕 header limit 和 body limit 设计一个小实验。连接数、fd 数、缓冲区、请求大小、队列长度和超时都是协议设计的一部分。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 29:关闭顺序
围绕 body limit 和 timeout 设计一个小实验。网络程序要区分停止接收新连接、排空已有连接、发送剩余响应、关闭写方向、释放 fd。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 30:跨平台
围绕 timeout 和 keep-alive 设计一个小实验。Linux 的 epoll、BSD/macOS 的 kqueue、Windows 的 IOCP 模型不同,C++ 抽象应隐藏平台差异但暴露语义差异。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 31:生产诊断
围绕 keep-alive 和 fuzz 设计一个小实验。日志要包含 connection id、peer address、状态、读写字节数、错误码和关闭原因。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 32:安全边界
围绕 fuzz 和 framing 设计一个小实验。任何来自网络的长度、头字段、路径、编码和压缩数据都不可信,必须先限制再分配。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。
深化切片 33:最小实验
围绕 framing 和 parser state 设计一个小实验。把一个复杂网络现象拆成一个客户端、一个服务端和一条可重复命令。先确认基础行为,再加并发、超时和异常输入。
one variable
-> one observation
-> one failure mode
-> one engineering rule复盘时要回答:这个结论属于 TCP 语义、socket API 语义、操作系统实现、应用协议,还是你自己写的 C++ 封装?如果分不清,协议分帧、HTTP 基础与服务加固 就还没有真正掌握。