基于poll()的多请求HTTP服务器阻塞问题排查求助
非阻塞HTTP服务器poll阻塞问题修复
问题背景
实现了基于poll的非阻塞HTTP服务器,期望处理多请求,但使用siege --verbose --reps="1000" --concurrent=100 http://localhost:8080测试时,服务器处理若干请求后阻塞,需手动终止进程。
关键错误分析
- 错误处理所有POLLIN事件:代码中只要fd触发POLLIN就调用
accept,但只有监听fd的POLLIN对应新连接,客户端fd的POLLIN是请求数据,错误调用accept会导致大量无效操作,甚至直接终止服务器。 - 客户端fd未设非阻塞:
accept返回的客户端fd默认是阻塞模式,recv操作会卡住整个进程,无法处理其他请求。 - 错误使用
MSG_OOB标志:send时用MSG_OOB发送带外数据,不符合HTTP规范,客户端无法正确解析响应,导致连接长期占用。 - accept错误处理不当:非阻塞监听fd在无新连接时
accept返回-1,此时errno为EAGAIN/EWOULDBLOCK,不应直接终止服务器。 - fd移除逻辑缺陷:移除fd后循环变量未调整,会跳过下一个fd的处理,残留无效连接。
- 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
相关产品推荐
相关产品推荐

