c++如何实现简单的HTTP服务器_c++ socket并发处理【实战】

3次阅读

合格起点是用阻塞socket配合select实现单线程http服务:监听fd非阻塞,clientfd阻塞;recv找”rnrn”解析路径,send失败时缓存并重试;关注EINTR、ECONNRESET、EMFILE等errno确保连接干净回收。

c++如何实现简单的HTTP服务器_c++ socket并发处理【实战】

socket + select 实现基础 HTTP 服务,别碰 epoll 或线程池起步

新手直接上 epollstd::Thread 池,90% 会卡在连接复用、请求解析边界、缓冲区截断上。先用阻塞式 socket 配合 select 做单线程轮询,能收发完整 HTTP 请求/响应,就是合格起点。

关键点:监听 socket 设为非阻塞(避免 accept 卡住),客户端 socket 保持阻塞(简化读取逻辑);每次 select 返回后只处理就绪的 fd,且对每个 fd 最多调用一次 recv 和一次 send,防止饿死其他连接。

  • listenfd 加入 readfds,用于接收新连接
  • 每个已连接的 clientfd 也加入 readfds,但仅当它尚未发送完响应时才加入 writefds
  • 每次 recv 后检查是否收到 "rnrn" —— 这是 HTTP header 结束标志,没收到就暂存到该连接的缓冲区
  • 不要假设一次 recv 能读完整个请求:HTTP/1.1 可能分片到达,必须拼接

手动解析 GET 请求路径,别依赖第三方库

初期目标不是支持全部 HTTP 方法或 header,而是让 curl http://127.0.0.1:8080/hello 返回 HTTP/1.1 200 OKrnContent-Length: 12rnrnHello World!。这就够了。

解析逻辑非常直白:从 recv 缓冲区开头找第一个空格,再找第二个空格,中间内容就是 path(如 "/hello")。注意跳过前导空格和换行,且 path 必须以 '/' 开头,否则返回 400。

立即学习C++免费学习笔记(深入)”;

  • std::String::find_first_of(" trn") 定位 method 结束位置
  • std::string::find_first_not_of(" trn", pos) 跳过空白后找 path 起始
  • 路径中若含 "..""%2e" 等编码绕过,先简单拒绝(返回 403),不急着做解码
  • 响应体长度必须严格匹配 Content-Length,少一个字节浏览器会一直转圈

并发瓶颈不在 socket,而在 send 的阻塞行为

你以为并发卡在 accept?其实更大概率是某个慢客户端(比如网络差或故意不读响应)导致 send(clientfd, ...) 阻塞,整条 select 循环被拖住。这是初学者最常忽略的点。

解决方案不是加线程,而是把 client socket 也设为非阻塞,并用 select 监控其 writefds。只有当 select 报告该 fd 可写时,才尝试发剩余数据;若 send 返回 EAGAINEWOULDBLOCK,就把待发数据缓存起来,等下次可写再续发。

  • 每次 send 后检查返回值:等于请求长度才算发完,小于则记录已发字节数,下次继续
  • 每个连接需维护自己的发送缓冲区(std::vector)和已发偏移量
  • 切勿在 send 失败后关闭连接——TCP 允许“半关闭”,对方可能还在发 FIN
  • linux 下可用 SO_SNDTIMEO 设置 send 超时,但不如非阻塞 + select 稳定

调试时必看的三个 errno

跑不通时,别只打印 “connection failed”——直接查 errno,大部分问题当场定位。

  • EINTRselectrecv 被信号中断,重试即可,不是错误
  • ECONNRESET:客户端突然断开(比如 curl 被 Ctrl+C),此时 recv 返回 -1,应清理对应连接状态
  • EMFILEENFILE:打开文件描述符超限,说明没及时 close(clientfd),尤其在 send 失败后容易漏关

真正难的从来不是写出能编译的代码,而是让每个连接在异常断开、分片到达、发送卡住时,状态都能干净回收。这些细节藏在 errno 和缓冲区管理里,而不是架构图上。

text=ZqhQzanResources