HTTP服务器poll()返回0导致阻塞问题求助
简易HTTP服务器poll()阻塞问题排查与修复
问题现象
开发简易HTTP服务器时,使用siege测试(参数:siege --file=url --verbose --reps --concurrent=200 --no-parser),服务器正常运行一段时间后出现阻塞,调试发现poll()返回0,无文件描述符触发事件。
问题代码
while (1) { int ret = poll(&this->test[0],this->test.size(),1000); if (ret == -1) { std::perror("webserv(poll)"); return ; } //else if (ret == 0) //{ std::cout << "size : " << this->test.size() << " and flags are : " << this->test[0].revents << std::endl; //continue; //} for (size_t i = 0; i < this->test.size(); i++) { if (this->test[i].revents & POLLHUP) { std::cout << "2" << std::endl; close(this->test[i].fd); this->test.erase(this->test.begin() + i); } else if (this->test[i].revents & POLLIN) { if (this->test[i].fd == 3) { std::cout << "3" << std::endl; socklen_t addr_size = sizeof client_struct; int client_fd = accept(this->test[i].fd, &client_struct,&addr_size); if (client_fd < 0) { perror("webserv(accept)"); return ; } assign_poll.fd = client_fd; assign_poll.events = POLLIN; this->test.push_back(assign_poll); } else { std::cout << "4" << std::endl; char buf[1000011]; ssize_t s = recv(this->test[i].fd,buf,1000000,0); buf[s] = 0; this->test[i].events = POLLOUT; std::cout << buf << std::endl; } } else if (this->test[i].revents & POLLOUT) { std::cout << "5" << std::endl; if (send(this->test[i].fd,msg.c_str(),msg.length(),0) == -1) { perror("webserv(send)"); return ; } close(this->test[i].fd); this->test.erase(this->test.begin() + i); } } }
核心问题分析
- 容器遍历索引错误:调用
erase删除元素后,容器后续元素前移,但循环的i仍递增,导致下一个元素被跳过,大量无效fd或未处理fd残留,最终让poll()无事件可触发。 - POLLHUP事件处理缺陷:POLLHUP可能与POLLIN/POLLOUT同时触发,当前if-else分支仅处理POLLHUP,且删除元素后的索引错位会引发更多漏处理。
- recv错误未处理:当
recv返回-1(如客户端异常断开),未关闭fd也未从poll列表移除,无效fd占用列表位置,干扰poll()正常检测。 - 监听fd硬编码:直接用
fd == 3判断监听套接字,移植性极差,fd分配变化后直接失效。 - revents未重置:修改events为POLLOUT后,revents标志未清空,可能导致下一次poll循环误判事件类型。
修复后的代码示例
// 提前定义成员变量存储监听fd,比如this->listen_fd while (1) { int ret = poll(&this->test[0], this->test.size(), 1000); if (ret == -1) { std::perror("webserv(poll)"); return; } // 用迭代器遍历,避免索引错位问题 for (auto it = this->test.begin(); it != this->test.end(); ) { struct pollfd &pfd = *it; bool erased = false; if (pfd.revents & POLLHUP) { std::cout << "POLLHUP triggered" << std::endl; close(pfd.fd); it = this->test.erase(it); erased = true; } else if (pfd.revents & POLLIN) { if (pfd.fd == this->listen_fd) // 替换硬编码的3 { std::cout << "Accept new client" << std::endl; socklen_t addr_size = sizeof(client_struct); int client_fd = accept(pfd.fd, &client_struct, &addr_size); if (client_fd < 0) { perror("webserv(accept)"); ++it; continue; } struct pollfd assign_poll; assign_poll.fd = client_fd; assign_poll.events = POLLIN; this->test.push_back(assign_poll); ++it; } else { std::cout << "Receive data" << std::endl; char buf[1000011]; ssize_t s = recv(pfd.fd, buf, 1000000, 0); if (s == -1) { std::perror("webserv(recv)"); close(pfd.fd); it = this->test.erase(it); erased = true; } else if (s == 0) { // 客户端正常断开 close(pfd.fd); it = this->test.erase(it); erased = true; } else { buf[s] = 0; std::cout << buf << std::endl; pfd.events = POLLOUT; pfd.revents = 0; // 重置revents避免误判 ++it; } } } else if (pfd.revents & POLLOUT) { std::cout << "Send response" << std::endl; ssize_t send_ret = send(pfd.fd, msg.c_str(), msg.length(), 0); if (send_ret == -1) { perror("webserv(send)"); close(pfd.fd); it = this->test.erase(it); erased = true; } else { close(pfd.fd); it = this->test.erase(it); erased = true; } } if (!erased) { ++it; } } }
关键修复点说明
- 迭代器遍历:用迭代器替代索引遍历,删除元素时直接更新迭代器,彻底避免索引错位问题。
- 完善错误处理:处理recv返回-1和0的情况,及时关闭fd并从poll列表移除,防止无效fd残留。
- 替换硬编码监听fd:用成员变量存储监听fd,提升代码可靠性和移植性。
- 重置revents:修改events后清空revents,防止下一次循环误判事件类型。
- POLLHUP处理优化:确保POLLHUP触发时正确清理资源,避免无效fd占用列表位置。
内容的提问来源于stack exchange,提问作者younes askour
相关产品推荐
相关产品推荐

