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

基于Boost.Asio的HTTP客户端长连接保持问题

解决Boost.Asio Reporter复用连接的问题

我来帮你理清楚当前代码的问题,然后一步步修改实现复用单例、保持TCP连接的需求:

问题根源

你现在每次循环都新建Reporter实例才能正常工作,核心原因有两个:

  1. HTTP请求头强制关闭连接:你在请求里写了Connection: close,服务器处理完请求后会主动断开TCP连接,所以第二次调用send时,旧的socket已经失效了。
  2. 请求缓冲区未重置:类成员的request streambuf在第一次发送后会残留旧数据,第二次发送时会把新旧数据混在一起,导致请求格式错误。

具体修改方案

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 06:54:56