监听端口前需设socket为非阻塞或用select/poll避免accept阻塞;bind前须setsockopt启用SO_REUSEADDR;HTTP解析须按\r\n切分、状态机处理TCP流式数据;响应必须严格遵循HTTP格式,含正确Content-Length与\r\n分隔。

用 socket 监听端口前必须设为非阻塞或正确处理 accept 阻塞
默认 socket 是阻塞的,accept() 会卡住主线程,无法响应其他连接或做请求解析。不加处理就写个死循环调 accept,服务器看起来“启动了”,但实际只能服务一个请求,后续连接全被丢弃或超时。
- 推荐方式:调用
fcntl(sockfd, F_SETFL, O_NONBLOCK)(Linux/macOS)或ioctlsocket(sockfd, FIONBIO, &nonblocking)(Windows)设为非阻塞 - 更稳妥的做法是搭配
select()或poll()等待可读事件,避免忙轮询;简单 demo 可先用阻塞模式 + 多线程,但生产环境必须异步或事件驱动 - 监听 socket 必须先
bind()再listen(),且bind()前要setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)),否则改代码重启时经常报Address already in use
HTTP 请求行和头部必须按 \r\n 切分,不能只认 \n
HTTP/1.1 规范明确要求行尾是 \r\n(CRLF),不是 Unix 风格的 \n。用 std::getline() 默认按 \n 分割,会导致读到 "GET / HTTP/1.1\r" 这种带残留 \r 的字符串,后续解析路径或方法失败。
- 读取原始字节流后,手动查找
"\r\n"位置比依赖std::getline更可靠 - 请求行至少含三部分:
method、path、version,中间用空格分隔;空行(即连续两个\r\n)标志着 headers 结束 - 常见错误:把整个
recv()缓冲区当完整请求处理——TCP 是流式协议,一次recv()可能只收到半行,也可能包含多个请求,必须缓存 + 状态机解析
返回响应必须严格遵循 HTTP 格式,否则浏览器直接白屏
哪怕只是返回 "Hello World",也要有状态行、Content-Length、空行和正文。少一个 \r\n,或 Content-Length 和实际字节数不符,Chrome/Firefox 就会卡在 loading 状态或显示 ERR_INVALID_HTTP_RESPONSE。
HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 12\r\n\r\nHello World\r\n
-
Content-Length值必须是响应体(不含 header)的字节数,不是字符串长度(注意std::string::length()对 UTF-8 是字节数,没问题;但若用wstring就错) - 务必以
\r\n\r\n结束 headers,之后紧跟 body;body 后**不要**额外加\r\n,否则算进Content-Length就错 - 如果想支持 Keep-Alive,需加
Connection: keep-alive并复用 socket,但简单场景建议每次响应后close(client_fd),避免连接堆积
没有 TLS 就别碰 443,本地开发用 8080 或 3000 更省事
绑定 80 或 443 端口在 Linux/macOS 需 root 权限,Windows 虽然宽松些但仍有策略限制。强行 sudo 运行 C++ 服务风险高,且和前端开发流程脱节。
立即学习“C++免费学习笔记(深入)”;
真正难的不是收发字节,是状态同步、缓冲管理、超时控制和并发安全。写完能跑通 GET 就够教学用,但上线前必须补上信号处理、日志、连接数限制和 request body 截断逻辑。











