基于Boost.Asio的HTTP客户端长连接保持问题
解决Boost.Asio Reporter复用连接的问题
我来帮你理清楚当前代码的问题,然后一步步修改实现复用单例、保持TCP连接的需求:
问题根源
你现在每次循环都新建Reporter实例才能正常工作,核心原因有两个:
- HTTP请求头强制关闭连接:你在请求里写了
Connection: close,服务器处理完请求后会主动断开TCP连接,所以第二次调用send时,旧的socket已经失效了。 - 请求缓冲区未重置:类成员的
requeststreambuf在第一次发送后会残留旧数据,第二次发送时会把新旧数据混在一起,导致请求格式错误。
具体修改方案
1. 切换到HTTP长连接
把请求头里的Connection: close改成Connection: keep-alive(或者直接用HTTP/1.1,它默认启用长连接),这样服务器会保持TCP连接,允许后续请求复用。
2. 每次发送前清空请求缓冲区
在send方法开头,调用request.consume(request.size())清空streambuf里的旧数据,确保每次发送的都是全新的请求内容。
3. 增加重连逻辑
即使使用长连接,服务器也可能因超时或异常断开连接,所以需要在捕获到连接错误时自动重新建立连接。
修改后的完整代码
首先是Reporter.hpp:
#include <boost/asio.hpp> #include <string> #include <iostream> class Reporter { public: Reporter(const std::string& host, const std::string& port); bool send(const std::string& json); private: bool reconnect(); // 辅助重连函数 std::string the_host; std::string the_port; boost::asio::io_service io_service; boost::asio::ip::tcp::socket socket; boost::asio::streambuf request; std::ostream request_stream; };
然后是实现文件:
#include "Reporter.hpp" #include <string> using boost::asio::ip::tcp; Reporter::Reporter(const std::string& host, const std::string& port) : the_host(host), the_port(port), socket(io_service), request_stream(&request) { // 构造时直接建立连接 if (!reconnect()) { throw std::runtime_error("Failed to initialize connection"); } } bool Reporter::reconnect() { try { // 先关闭旧连接(如果存在) if (socket.is_open()) { boost::system::error_code ec; socket.shutdown(tcp::socket::shutdown_both, ec); socket.close(ec); } tcp::resolver resolver(io_service); tcp::resolver::query query(the_host, the_port); auto endpoint_iterator = resolver.resolve(query); boost::asio::connect(socket, endpoint_iterator); std::cout << "Connected to server successfully" << std::endl; return true; } catch (const std::exception& e) { std::cout << "Reconnect failed: " << e.what() << std::endl; return false; } } bool Reporter::send(const std::string& json) { try { // 清空请求缓冲区,避免旧数据残留 request.consume(request.size()); // 构造HTTP/1.1请求(默认支持长连接) request_stream << "POST / HTTP/1.1\r\n"; request_stream << "Host: " << the_host << "\r\n"; request_stream << "Content-Type: application/json\r\n"; request_stream << "Content-Length: " << json.length() << "\r\n"; request_stream << "Accept: */*\r\n"; request_stream << "Connection: keep-alive\r\n\r\n"; request_stream << json; // 发送请求 boost::asio::write(socket, request); // 读取响应状态行 boost::asio::streambuf response; boost::system::error_code ec; boost::asio::read_until(socket, response, "\r\n", ec); if (ec) { std::cout << "Read failed: " << ec.message() << ", trying to reconnect..." << std::endl; // 重连后重试一次发送 return reconnect() ? send(json) : false; } // 校验响应状态 std::istream response_stream(&response); std::string http_version; response_stream >> http_version; unsigned int status_code; response_stream >> status_code; std::string status_message; std::getline(response_stream, status_message); if (!response_stream || http_version.substr(0, 5) != "HTTP/") { std::cout << "Invalid response received" << std::endl; return false; } if (status_code != 200) { std::cout << "Server returned status code: " << status_code << std::endl; return false; } // 读取响应头 boost::asio::read_until(socket, response, "\r\n\r\n", ec); if (ec) { std::cout << "Failed to read response headers: " << ec.message() << std::endl; return false; } // 打印响应头(可选) std::string header; while (std::getline(response_stream, header) && header != "\r") { std::cout << header << std::endl; } std::cout << std::endl; // 打印响应内容 if (response.size() > 0) { std::cout << &response; } // 读取剩余响应内容直到EOF while (boost::asio::read(socket, response, boost::asio::transfer_at_least(1), ec)) { std::cout << &response; } if (ec != boost::asio::error::eof) { throw boost::system::system_error(ec); } return true; } catch (const std::exception& e) { std::cout << "Send failed with exception: " << e.what() << std::endl; // 发生异常后尝试重连,为下一次发送做准备 reconnect(); return false; } } int main(int argc, char **argv) { try { Reporter wr("localhost", "3000"); int i = 0; while(i < 20000) { std::string json = "{\"name\":\"avron\"}"; std::cout << "\nSending [" << i << "]\n"; wr.send(json); i++; sleep(1); } } catch (const std::exception& e) { std::cout << "Initialization failed: " << e.what() << std::endl; return 1; } return 0; }
额外注意事项
- 确保你的服务器支持HTTP长连接:大部分现代Web服务器(Nginx、Express等)都默认支持,但如果是自定义服务器,需要处理
Connection: keep-alive头并保持连接。 - 长连接超时:服务器通常会对空闲的长连接设置超时(比如Nginx默认是65秒),你的代码每秒发送一次请求,不会触发超时,但如果发送间隔变长,需要考虑自动重连。
- 同步IO vs 异步IO:当前用的是同步IO,逻辑简单直观;如果需要更高性能(比如同时处理多个连接),可以考虑切换到Boost.Asio的异步API,但对于你的每秒发送一次的需求,同步方式完全足够。
内容的提问来源于stack exchange,提问作者avrono
相关产品推荐
相关产品推荐

