创建监听套接字必须设为非阻塞并配合epoll/select,解析HTTP需循环读取拼接缓冲区、正确处理分块和Content-Length,路径须标准化校验防遍历,响应头尾严格用\r\n\r\n。

用 socket() 创建监听套接字时,必须设为非阻塞或配合 select()/epoll()
直接调用 accept() 阻塞主线程会导致服务器无法处理多个请求。哪怕只支持并发 2 个连接,也得避免卡死。
- Linux 下推荐用
epoll():先epoll_create1(0),再epoll_ctl()注册监听 socket 的EPOLLIN事件 - Windows 可用
WSAEventSelect()或select(),但注意select()的fd_set有 64 限制 - 务必对监听 socket 调用
fcntl(fd, F_SETFL, O_NONBLOCK)(Linux)或ioctlsocket(s, FIONBIO, &nonblocking)(Win) - 忽略非阻塞设置,
accept()可能返回EAGAIN或WSAEWOULDBLOCK,不是错误,要继续轮询
解析 HTTP 请求行和头部时,不能假设一行一个 \r\n
客户端可能发来不带 \r 的 \n(比如某些调试工具),也可能粘包——一次 recv() 读到多个请求,或一个请求分多次到达。
- 用循环读取 + 缓冲区拼接:每次
recv()追加到std::string buffer,然后查找"\r\n\r\n"边界 - 解析请求行用
std::istringstream或std::string::find_first_of(" ")拆出方法、路径、版本,别用strtok - 头部字段大小写不敏感,但值可能含空格(如
Content-Type: text/html; charset=utf-8),需完整提取冒号后内容并 trim - 遇到
Transfer-Encoding: chunked就得实现分块解析,简单服务器可直接拒绝(返回411 Length Required或501 Not Implemented)
发送响应时,Content-Length 必须精确,且响应体不能多发或少发字节
浏览器会根据 Content-Length 截断数据,多发字节会被当作文档一部分渲染,少发则触发超时或乱码。
- 静态文件响应前,先
stat()获取文件大小,填入Content-Length;生成内容(如目录列表)需先写入内存 buffer 再计算长度 - 响应头结尾必须是
"\r\n\r\n",之后紧跟二进制响应体——不要用std::endl,它在某些平台输出\n\n - 用
send(fd, buf.c_str(), buf.size(), MSG_NOSIGNAL)发送,检查返回值是否等于buf.size();不等说明没发完,需缓存剩余部分,下次EPOLLOUT触发时重试 - 别忘了设置
Connection: close(简易版可不支持 keep-alive),否则客户端可能一直等待下个请求
处理路径遍历(../)和空字节注入是安全底线
用户请求 GET /../../etc/passwd HTTP/1.1 或 /index.html%00.css,不做校验就会泄露任意文件。
立即学习“C++免费学习笔记(深入)”;
实际跑起来最易卡在缓冲区管理——HTTP 是文本协议但传输是字节流,边界模糊、编码混杂、错误容忍低。把 recv/send 和状态机拆清楚,比堆功能重要得多。










