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

boost::asio async_connect跨Linux网络命名空间连接异常问题

Boost.Asio服务器连接错误:Cannot assign requested address

问题场景

  • 触发条件:服务器绑定到单个核心且该核心CPU使用率达100%,同时出现周期性吞吐量下降时,async_connect调用抛出错误「Error connecting to localhost: Cannot assign requested address」
  • 环境差异:仅在服务器与目标服务器处于不同Linux网络命名空间时出现,同命名空间或本地环境下无异常

服务器代码

#include <iostream>
#include <string>
#include <boost/asio.hpp>
#include <boost/asio/thread_pool.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/bind.hpp>
#include "http_server.hh"

using namespace boost::asio;
using ip::tcp;

std::string desired_IP_address = "172.16.0.2"; // For example purposes

class Session : public std::enable_shared_from_this<Session> {
public:
    Session(ip::tcp::resolver& resolver,tcp::socket socket, tcp::socket client_socket) : socket_(std::move(socket)), resolver_(resolver), client_socket_(std::move(client_socket)) {}

    void start() { 
        do_read();
    }

private:
    void do_read() {
        auto self(shared_from_this());
        socket_.async_read_some(
            boost::asio::buffer(data_),
            [this, self](boost::system::error_code ec, std::size_t length) {
                if (!ec) {
                    std::string request(data_.data(), length);
                    path_ = extract_path(request);
                    resolver_.async_resolve(
                        ip::tcp::resolver::query("172.16.0.4", "5000"),
                        [this,self](const boost::system::error_code& ec,
                            ip::tcp::resolver::iterator it) {
                            if (ec) {
                                std::cout  << "Error resolving " << "localhost" << ": "
                                        << ec.message()<< std::endl;
                                return;
                            }

                            // For simplicity, we'll assume the first endpoint will always
                            // be available.
                            //std::cout << "localhost" << ": resolved to " << it->endpoint()
                            //        << std::endl;
                            do_connect(it->endpoint());
                        });
                    //handle_request_async(path);
                }
            });
    }

    void do_connect(const ip::tcp::endpoint& dest) {
        // Remember that the Asio library will make copies of parameters passed
        // by const reference, so it's ok to let the endpoint go out of scope
        // when this method returns.
        auto self(shared_from_this());
        boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string("172.16.0.4"), 5000);
        client_socket_.async_connect(
            endpoint, [this, self](const boost::system::error_code& ec) {
                if (ec) {
                    std::cout << "Error connecting to " << "localhost"<< ": "
                               << ec.message()<< std::endl;
                    return;
                }

                //std::cout << "localhost" << ": connected to "
                //          << client_socket_.remote_endpoint() << std::endl;
                do_send_http_get();
            });
    }

    void do_send_http_get() {
        auto self(shared_from_this());
        // At minimum, the remote server needs to know the path being fetched
        // and the host serving that path. The latter is required because a
        // single server often hosts multiple domains.
        request_ = std::string("GET /") + " HTTP/1.1\r\nHost: " + "example.com" + "\r\n\r\n";
        async_write(
            client_socket_, buffer(request_),
            [this,self](const boost::system::error_code& ec, std::size_t size) {
                if (ec) {
                    std::cout << "Error sending GET " << ec<< std::endl;
                    return;
                }

                //std::cout << "localhost" << ": sent " << size << " bytes"<< std::endl;
                do_recv_http_get_header();
            });
    }

    void do_recv_http_get_header() {
        // Since HTTP/1.1 is a text based protocol, most of it is human readable
        // by design. Notice how the "double end of line" character sequence
        // ("\r\n\r\n") is used to delimit message sections.
        auto self(shared_from_this());
        async_read_until(
            client_socket_, response_, "\r\n\r\n",
            [this, self](const boost::system::error_code& ec, std::size_t size) {
                if (ec) {
                    std::cout << "Error receiving GET header " << ec;
                    return;
                }

                //std::cout << "localhost:5000" << ": received " << size << ", streambuf "
                //          << response_.size();

                // The asio::streambuf class can use multiple buffers
                // internally, so we need to use a special iterator to copy out
                // the header.
                std::string header(
                    buffers_begin(response_.data()),
                    buffers_begin(response_.data()) + size);
                response_.consume(size);

                //std::cout << "----------" << std::endl << "localhost:5000"
                //          << ": header length " << header.size() << std::endl
                //          << header << std::endl;

                // First we'll check for the explicit "Content-Length" length
                // field. This provides the exact body length in bytes.
                size_t pos = header.find("Content-Length: ");
                if (pos != std::string::npos) {
                    size_t len = std::strtoul(
                        header.c_str() + pos + sizeof("Content-Length: ") - 1,
                        nullptr, 10);
                    do_receive_http_get_body(len - response_.size());
                    return;
                }

                // The other alternative is a chunked transfer. There is a quick
                // way to determine the remaining length in this case.
                pos = header.find("Transfer-Encoding: chunked");
                if (pos != std::string::npos) {
                    do_receive_http_get_chunked_body();
                    return;
                }

                std::cout << "Unknown body length";
            });
    }

    void do_receive_http_get_body(size_t len) {
        // For "Content-Length" we know exactly how many bytes are left to
        // receive.
        auto self(shared_from_this());
        async_read(
            client_socket_, response_, transfer_exactly(len),
            [this,self] (const boost::system::error_code& ec, std::size_t size) {
               handle_http_get_body(ec, size);
            });
    }

    void do_receive_http_get_chunked_body() {
        // For chunked transfers the final body chunk will be terminated by
        // another "double end of line" delimiter.
        auto self(shared_from_this());
        async_read_until(
            client_socket_, response_, "\r\n\r\n",
            [this,self] (const boost::system::error_code& ec, std::size_t size) {
                handle_http_get_body(ec, size);
            });
    }

    void handle_http_get_body(const boost::system::error_code& ec,
                              std::size_t size) {
        if (ec) {
            std::cout << "Error receiving GET body " << ec;
            return;
        }

        //std::cout << "localhost:5000" << ": received " << size << ", streambuf "
        //          << response_.size();

        // We can finally consume the body and print it out if desired.
        const auto& data = response_.data();
        std::string response_body(buffers_begin(data), buffers_end(data));
        response_.consume(size);

        //std::cout << "----------" << std::endl << "localhost:5000" << ": body length "
        //          << response_body.size() << std::endl;
        //std::cout << response_body << std::endl;
        handle_request_async();
    }


    void handle_request_async() {
        auto self(shared_from_this());

        async_response([this,self](const std::string& response) {
            // std::cout << "path " <<  path_ << std::endl;
            // std::cout << response << std::endl;
            async_write(socket_, boost::asio::buffer(response),
                [this,self](boost::system::error_code ec, std::size_t /*length*/) {
                    if (!ec) {
                        //std::cout << "response" << std::endl;
                        boost::system::error_code ignored_ec;
                        socket_.shutdown(tcp::socket::shutdown_both, ignored_ec);
                    }
                });
        });
    }

    void async_response(std::function<void(const std::string&)> callback) {
        // Assuming handle_request returns HTTP_Response asynchronously
        HTTP_Response* htmlResponse = handle_request(path_);
        //std::cout << "path: " << path << std::endl;
        std::string response = htmlResponse->body;
        //std::cout << "content: " << response << std::endl;
        callback(response);
        
        delete htmlResponse;
    }

    // Function to extract path from the HTTP request
    std::string extract_path(const std::string& request) {
        // Logic to extract path from the request string
        // Example logic: extracting the path after the GET method
        std::string path;
        //std::cout << request << std::endl;
        // Implement your path extraction logic here

        // Find the end of the request line (the first line of the HTTP request)
        std::size_t requestLineEnd = request.find("\r\n");
        if (requestLineEnd != std::string::npos) {
            std::string requestLine = request.substr(0, requestLineEnd);

            // Split the request line into parts (method, path, protocol)
            std::vector<std::string> parts;
            boost::split(parts, requestLine, boost::is_any_of(" "));

            // The second part typically contains the path (e.g., "GET /path HTTP/1.1")
            if (parts.size() >= 2) {
                path = parts[1]; // Extract the path from the request line
            }
        }

        return path;
    }

    tcp::socket socket_;
    tcp::socket client_socket_;
    std::array<char, 8192> data_;
    std::string path_;

    std::string request_;
    boost::asio::streambuf response_;
    ip::tcp::resolver& resolver_;

};

class Server {
public:
    Server(boost::asio::io_context& io_context, short port)
        : acceptor_(io_context, tcp::endpoint(boost::asio::ip::make_address(desired_IP_address), port)),
          //acceptor_(io_context, tcp::endpoint(tcp::v4(), port)),
          socket_(io_context), resolver_(io_context), client_socket_(io_context)
    {
        do_accept();
    }

private:
    void do_accept() {
        acceptor_.async_accept(
            socket_,
            [this] (boost::system::error_code ec) {
                if (!ec) {
                    std::make_shared<Session>(resolver_, std::move(socket_), std::move(client_socket_))->start();
                }
                do_accept();
            });
    }

    tcp::acceptor acceptor_;
    tcp::socket socket_;
    tcp::socket client_socket_;
    ip::tcp::resolver resolver_;
};

int main() {
    try {
        boost::asio::io_context io_context;
        Server server(io_context, 8080);
        boost::asio::thread_pool pool(40);
        for (std::size_t i = 0; i < 40; ++i)
            boost::asio::post(pool, [&io_context]() { io_context.run(); });

        pool.join();
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}

问题根源分析

  1. CPU满载导致网络栈调度阻塞:单个核心100%占用时,Linux内核的软中断(负责处理网络包收发)无法获得足够CPU时间,TCP连接的SYN握手流程超时,内核返回「Cannot assign requested address」错误。
  2. 跨命名空间的资源竞争放大:不同网络命名空间下,端口分配、路由转发等操作依赖内核跨命名空间调度,CPU满载时这类操作的延迟远超TCP连接超时阈值,触发错误。
  3. 代码中的致命缺陷:
    • Server类复用client_socket_给所有Session,导致多个Session共享同一个socket资源,连接状态混乱,在高负载下极易触发资源分配失败。
    • do_connect方法硬编码目标地址,忽略了resolver_async_resolve返回的解析结果,无法适配动态网络环境。

修复方案

1. 修复Socket复用问题

修改Server类的do_accept方法,为每个Session创建独立的客户端socket,移除类中的client_socket_成员变量:

class Server {
public:
    Server(boost::asio::io_context& io_context, short port)
        : acceptor_(io_context, tcp::endpoint(boost::asio::ip::make_address(desired_IP_address), port)),
          socket_(io_context), resolver_(io_context)
    {
        do_accept();
    }

private:
    void do_accept() {
        acceptor_.async_accept(
            socket_,
            [this](boost::system::error_code ec) {
                if (!ec) {
                    // 为每个Session创建独立的client_socket
                    std::make_shared<Session>(resolver_, std::move(socket_), tcp::socket(acceptor_.get_executor()))->start();
                }
                do_accept();
            });
    }

    tcp::acceptor acceptor_;
    tcp::socket socket_;
    ip::tcp::resolver resolver_;
};

2. 使用解析后的目标端点

修改Session::do_connect方法,使用解析得到的dest参数,避免硬编码地址:

void do_connect(const ip::tcp::endpoint& dest) {
    auto self(shared_from_this());
    client_socket_.async_connect(
        dest, [this, self](const boost::system::error_code& ec) {
            if (ec) {
                std::cout << "Error connecting to " << dest.address().to_string() << ":" << dest.port() << ": "
                          << ec.message() << std::endl;
                return;
            }
            do_send_http_get();
        });
}

3. 缓解CPU满载影响

  • 取消服务器进程的单核心绑定,使用taskset分配多个核心,例如:taskset -c 0-3 ./your_server
  • 优化业务逻辑,减少CPU密集型操作,避免核心长期处于100%占用状态
  • 调整TCP连接超时参数,通过socket选项延长超时时间:
    client_socket_.set_option(boost::asio::socket_base::send_timeout(std::chrono::seconds(10)));
    client_socket_.set_option(boost::asio::socket_base::receive_timeout(std::chrono::seconds(10)));
    

4. 跨命名空间网络优化

  • 检查跨命名空间的路由配置,确保网络连通性正常,无丢包或高延迟问题
  • 调整本地端口池范围,扩大可用端口数量:
    echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 19:39:53