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

如何让Web服务器支持HTTP 1.1持久连接?现有实现问题咨询

问题分析与解决方案

核心问题点

1. ET模式下recv返回值误判

你当前只要recv返回小于1就直接断开连接,但ET模式下非阻塞socket的recv返回-1有两种完全不同的情况:

  • EAGAIN/EWOULDBLOCK:当前socket缓冲区没有数据,属于ET模式下的正常状态,不是客户端断开。
  • 其他错误(如ECONNRESET)或返回0:才是客户端真的断开连接。

你的代码把所有recv<1的情况都当成断开,直接close,这是长连接失效的主要原因。

2. 错误移除epoll监听

你担心多线程操作同一socket,但ET模式下,只要socket的EPOLLIN事件对应的缓冲区数据没被读完,epoll不会重复触发该事件。一开始就移除监听,导致处理完当前请求后,后续客户端发的请求根本无法被epoll捕获,线程只能死循环recv,拿到EAGAIN就误判断开。

3. HTTP请求读取逻辑错误

你靠读取字节数(read_size == 1024)判断请求头是否完整,这完全不符合HTTP协议规范——HTTP请求头的结束标志是\r\n\r\n,和读取的字节数无关。这种写法要么会截断不完整的请求头,要么会多读后续请求的内容(长连接下多个请求在同一个socket流里),导致请求解析混乱。

4. 长连接响应头缺失

HTTP 1.1默认是长连接,但需要服务器在响应头中返回Connection: keep-alive(除非客户端请求Connection: close)。如果你的write_client没加这个头,客户端可能会主动断开连接。


修正方案

1. 正确处理recv返回值与请求读取逻辑

void Handling_connections(int client_socket_fd)
{
    // 不要一开始就移除epoll监听!
    // DEL_epoll_evs(client_socket);

    // 保存长连接下未处理完的请求残留数据
    std::string leftover_data;

    while (true) {
        char buf[1024];
        memset(buf, 0, sizeof(buf));
        ssize_t read_size = recv(client_socket_fd, buf, sizeof(buf), 0);

        if (read_size == 0) {
            // 客户端正常关闭连接
            std::cout << "Client disconnected normally\n";
            close(client_socket_fd);
            return;
        } else if (read_size < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                // 当前无数据,将socket留在epoll等待下一次请求触发
                // ET模式下确保监听事件包含EPOLLET
                ADD_epoll_evs(client_socket_fd, EPOLLIN | EPOLLET);
                return;
            } else {
                // 连接出错,断开
                std::cout << "Connection error, errno: " << errno << "\n";
                close(client_socket_fd);
                return;
            }
        }

        // 拼接已读数据(加上之前残留的内容)
        std::string current_data = leftover_data + std::string(buf, read_size);
        leftover_data.clear();

        // 循环查找完整的HTTP请求头(处理长连接下的多个请求)
        size_t header_end_pos = current_data.find("\r\n\r\n");
        while (header_end_pos != std::string::npos) {
            // 提取完整的请求头
            std::string full_header = current_data.substr(0, header_end_pos + 4);
            // 解析并处理请求
            http_header tmp_header(full_header);
            write_client(tmp_header);

            // 处理剩余数据(可能是下一个请求的开头)
            current_data = current_data.substr(header_end_pos + 4);
            header_end_pos = current_data.find("\r\n\r\n");
        }

        // 保存未处理完的数据,下次读取时拼接
        leftover_data = current_data;
    }
}

2. 调整epoll监听逻辑

  • 不要在处理连接时移除epoll监听,ET模式下同一socket的EPOLLIN事件只会触发一次,直到你把缓冲区的数据读完。
  • 处理完当前所有请求后,如果还有未读完的数据(leftover_data不为空),继续循环处理;如果没有数据,就把socket留在epoll里,等待下一次客户端发请求触发。

3. 完善write_client的响应头

在生成HTTP响应时,检查客户端请求头的Connection字段:

  • 如果客户端请求Connection: close,响应头返回Connection: close,处理完后关闭socket。
  • 否则,响应头返回Connection: keep-alive,保持连接。

关键注意事项

  • ET模式下必须一次性把socket缓冲区的数据读完,否则epoll不会再次触发该socket的事件。
  • 长连接下要保存未处理完的请求数据,避免截断或丢失后续请求。
  • 永远不要忽略recv返回-1时的errno判断,这是区分“无数据”和“连接错误”的关键。

内容的提问来源于stack exchange,提问作者Nullptr

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 19:53:12