如何让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
相关产品推荐
相关产品推荐

