You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
        }
    }
}

核心问题分析

  1. 容器遍历索引错误:调用erase删除元素后,容器后续元素前移,但循环的i仍递增,导致下一个元素被跳过,大量无效fd或未处理fd残留,最终让poll()无事件可触发。
  2. POLLHUP事件处理缺陷:POLLHUP可能与POLLIN/POLLOUT同时触发,当前if-else分支仅处理POLLHUP,且删除元素后的索引错位会引发更多漏处理。
  3. recv错误未处理:当recv返回-1(如客户端异常断开),未关闭fd也未从poll列表移除,无效fd占用列表位置,干扰poll()正常检测。
  4. 监听fd硬编码:直接用fd == 3判断监听套接字,移植性极差,fd分配变化后直接失效。
  5. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 21:40:25