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

基于poll()的多请求HTTP服务器阻塞问题排查求助

非阻塞HTTP服务器poll阻塞问题修复

问题背景

实现了基于poll的非阻塞HTTP服务器,期望处理多请求,但使用siege --verbose --reps="1000" --concurrent=100 http://localhost:8080测试时,服务器处理若干请求后阻塞,需手动终止进程。

关键错误分析

  1. 错误处理所有POLLIN事件:代码中只要fd触发POLLIN就调用accept,但只有监听fd的POLLIN对应新连接,客户端fd的POLLIN是请求数据,错误调用accept会导致大量无效操作,甚至直接终止服务器。
  2. 客户端fd未设非阻塞:accept返回的客户端fd默认是阻塞模式,recv操作会卡住整个进程,无法处理其他请求。
  3. 错误使用MSG_OOB标志:send时用MSG_OOB发送带外数据,不符合HTTP规范,客户端无法正确解析响应,导致连接长期占用。
  4. accept错误处理不当:非阻塞监听fd在无新连接时accept返回-1,此时errno为EAGAIN/EWOULDBLOCK,不应直接终止服务器。
  5. fd移除逻辑缺陷:移除fd后循环变量未调整,会跳过下一个fd的处理,残留无效连接。
  6. recv无错误处理:recv返回-1(如客户端断开)时,直接操作buf会导致内存越界,未正确清理连接。

修复方案及修正代码

#include <cstdio>
#include <netinet/in.h>
#include <sys/_types/_socklen_t.h>
#include <sys/_types/_ssize_t.h>
#include <sys/fcntl.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <fcntl.h>
#include <poll.h>
#include <iostream>
#include <unistd.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <errno.h>

int create_connection(int port)
{
    int yes=1;
    struct sockaddr_in host_addr;
    int listen_fd = socket(AF_INET,SOCK_STREAM,0);
    if (listen_fd == -1)
    {
        perror("webserv(socket)");
        return -1;
    }
    host_addr.sin_family = AF_INET;
    host_addr.sin_port = htons(port);
    host_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    int host_addrlen = sizeof(host_addr);

    if (setsockopt(listen_fd,SOL_SOCKET, SO_REUSEADDR,&yes,sizeof(int)) != 0)
    {
        perror("webserv(setsockopt)");
        return -1;
    }
    if (bind(listen_fd,(struct sockaddr *)&host_addr,host_addrlen) != 0)
    {
        perror("webserv(bind)");
        return -1;
    }

    // 设置监听fd为非阻塞
    if (fcntl(listen_fd,F_SETFL,O_NONBLOCK) == -1)
    {
        perror("webserv(fcntl)");
        return -1;
    }

    if (listen(listen_fd,128) == -1)
    {
        perror("webserv(listen)");
        return -1;

    }
    return listen_fd;
}

void run_server(int listen_fd)
{
    int nb_of_fds = 0;
    struct pollfd fds[1024];
    fds[nb_of_fds].fd = listen_fd;
    fds[nb_of_fds].events = POLLIN;
    nb_of_fds++;
    struct sockaddr_storage communication; // 使用sockaddr_storage适配不同地址类型

    std::ifstream f("./younes/www/html/index.html");
    std::string body = "<html>  <body> <h1>test<h1> </body> </html>\r\n";
    if (f)
    {
        std::ostringstream ss;
        ss << f.rdbuf();
        body = ss.str();
    }
    std::string msg = "HTTP/1.1 200 OK\r\n";
    msg += "Content-Type: text/html\r\n";
    msg += "Content-Length:" + std::to_string(body.length()) + "\r\n";
    msg += "Connection: close\r\n"; // 明确关闭连接,避免客户端保持长连接
    msg += "\r\n";
    msg += body;

    while (1)
    {
        int ret = poll(fds,nb_of_fds,2000);
        if (ret == -1)
        {
            perror("webserv(poll)");
            return ;
        }
        else if (!ret)
            continue;
        else
        {
            for (int i = 0; i < nb_of_fds;i++)
            {
                if (fds[i].revents & POLLIN)
                {
                    if (fds[i].fd == listen_fd)
                    {
                        // 处理新连接
                        socklen_t addr_size = sizeof communication;
                        int fd_client = accept(listen_fd,(struct sockaddr *)&communication,&addr_size);
                        if (fd_client == -1)
                        {
                            // 非阻塞模式下无新连接时忽略,仅处理真正的错误
                            if (errno != EAGAIN && errno != EWOULDBLOCK)
                            {
                                perror("webserv(accept)");
                                return ;
                            }
                            continue;
                        }

                        // 设置客户端fd为非阻塞
                        if (fcntl(fd_client,F_SETFL,O_NONBLOCK) == -1)
                        {
                            perror("webserv(fcntl client)");
                            close(fd_client);
                            continue;
                        }

                        // 添加客户端fd到poll列表,监听POLLIN
                        fds[nb_of_fds].fd = fd_client;
                        fds[nb_of_fds].events = POLLIN;
                        nb_of_fds++;
                    }
                    else
                    {
                        // 处理客户端请求数据
                        char buf[1000011];
                        ssize_t s = recv(fds[i].fd,buf,1000000,0);
                        if (s <= 0)
                        {
                            // 客户端断开或出错,关闭并移除fd
                            close(fds[i].fd);
                            // 移除fd
                            for (int j = i;j < nb_of_fds - 1;j++)
                            {
                                fds[j] = fds[j + 1];
                            }
                            nb_of_fds--;
                            i--; // 调整循环索引,避免跳过下一个fd
                            continue;
                        }
                        buf[s] = 0;

                        // 请求读取完成,改为监听POLLOUT准备发送响应
                        fds[i].events = POLLOUT;
                    }
                }
                if (fds[i].revents & POLLOUT)
                {
                    // 发送响应,移除MSG_OOB标志
                    ssize_t sent = send(fds[i].fd,msg.c_str(),msg.length(),0);
                    if (sent == -1)
                    {
                        perror("webserv(send)");
                        close(fds[i].fd);
                    }
                    else if (sent < msg.length())
                    {
                        // 部分发送,继续监听POLLOUT(简化处理,实际需记录已发送长度)
                        fds[i].events = POLLOUT;
                        continue;
                    }

                    // 发送完成,关闭并移除fd
                    close(fds[i].fd);
                    for (int j = i;j < nb_of_fds - 1;j++)
                    {
                        fds[j] = fds[j + 1];
                    }
                    nb_of_fds--;
                    i--; // 调整循环索引
                }
                // 处理错误事件,比如客户端断开
                if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL))
                {
                    close(fds[i].fd);
                    for (int j = i;j < nb_of_fds - 1;j++)
                    {
                        fds[j] = fds[j + 1];
                    }
                    nb_of_fds--;
                    i--;
                }
            }
        }
    }
}

int main (int argc, char *argv[])
{
    int listen_fd;

    if (argc == 2)
        listen_fd = create_connection(atoi(argv[1]));
    else
        listen_fd = create_connection(8080);

    if (listen_fd > 0)
    {
        run_server(listen_fd);
        close(listen_fd);
    }
    return 0;
}

额外说明

  • 添加了Connection: close响应头,明确告知客户端关闭连接,避免长连接残留。
  • 处理了POLLERR/POLLHUP/POLLNVAL等错误事件,及时清理无效连接。
  • 简化了部分发送的处理(实际生产环境需记录已发送字节数,下次继续发送剩余部分)。

内容的提问来源于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.18 15:47:01