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; }
问题根源分析
- CPU满载导致网络栈调度阻塞:单个核心100%占用时,Linux内核的软中断(负责处理网络包收发)无法获得足够CPU时间,TCP连接的SYN握手流程超时,内核返回「Cannot assign requested address」错误。
- 跨命名空间的资源竞争放大:不同网络命名空间下,端口分配、路由转发等操作依赖内核跨命名空间调度,CPU满载时这类操作的延迟远超TCP连接超时阈值,触发错误。
- 代码中的致命缺陷:
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
相关产品推荐
相关产品推荐

