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

启动Boost.Asio TCP客户端触发boost::system::system_error异常求助

Boost.Asio类Redis TCP客户端服务器报错修复方案

问题现象

基于Boost.Asio开发类Redis的TCP客户端服务器应用,服务器启动后可正常监听(netstat显示处于LISTEN状态),但客户端启动后立即抛出错误:

terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
  what():  close: Bad file descriptor [system:9]

相关代码

client.h

#ifndef CLIENT_IMPL_H
#define CLIENT_IMPL_H

#include <boost/asio.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <cstddef>
#include <string>

namespace io = boost::asio;
using tcp = io::ip::tcp;
using error_code = boost::system::error_code;
using namespace std::placeholders;

class client : public boost::enable_shared_from_this<client> {

public:

    client(io::io_context &io_context, std::string const &addr, uint16_t port) 
    : socket_(io_context) {
        start(tcp::endpoint(
            io::ip::address::from_string(addr),
            port
        ));
    }

    void start(tcp::endpoint ep);

private:

    void on_connect(error_code const &err);

    void on_read(error_code const &err, std::size_t bytes_transmitted);
    void async_read();

    void on_write(error_code const &err);
    void async_write();

private:

    tcp::socket socket_;
    io::streambuf streambuf_;
    std::string request_;
    std::string response_;

};

#endif

client.cpp

#include <iostream>

#include "client_impl.h"

void client::start(tcp::endpoint ep) {
    socket_.async_connect(
        ep,
        [this](error_code err) {
            on_connect(err);
        }
    );
}

void client::on_connect(error_code const &err) {
    if (!err) {
        async_write();
    } else {
        socket_.close();
    }
}

void client::on_read(error_code const &err, std::size_t bytes_transmitted) {
    if (!err) {
        std::stringstream message(response_);
        message << std::istream(&streambuf_).rdbuf();
        streambuf_.consume(bytes_transmitted);

        std::cout << response_ << std::endl;
        response_.clear();

        async_write();
    } else {
        socket_.close();
    }
}

void client::async_read() {
    auto self = shared_from_this();
    io::async_read_until(
        socket_,
        streambuf_,
        "\n",
        [self](error_code err, std::size_t bytes_transmitted) {
            self->on_read(err, bytes_transmitted);
        }
    );
}

void client::on_write(error_code const &err) {
    if (!err) {
        async_read();
    } else {
        socket_.close();
    }
}

void client::async_write() {
    std::getline(std::cin, request_);
    request_ += "\n";
    io::async_write(
        socket_,
        io::buffer(request_),
        [this](error_code err, std::size_t bytes_transmitted) {
            on_write(err);
        }
    );
}

server.h

#ifndef SERVER_IMPL_H
#define SERVER_IMPL_H

#include <boost/asio.hpp>
#include <boost/asio/io_context.hpp>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
#include <regex>


namespace io = boost::asio;
using tcp = io::ip::tcp;
using error_code = boost::system::error_code;
using namespace std::placeholders;

class session : public std::enable_shared_from_this<session> {

public:
    session(tcp::socket socket, std::unordered_map<std::string, std::string> &storage) : 
        socket_(std::move(socket)),
        storage_ {storage} {}

    void start();

private:

    void async_read();
    void on_read(error_code err, std::size_t bytes_transmitted);

    void async_write();
    void on_write(error_code err, std::size_t bytes_transmitted);

    void message_handler(std::string const &message);
    void on_put(std::string const &message);
    void on_get(std::string const &message);
    void on_del(std::string const &message);
    void on_count(std::string const &message);
    void on_dump(std::string const &message);
    bool is_valid_key(std::string const &key);
    bool is_valid_txt(std::string const &fname);

    std::string get_from_storage(std::string const &key);
    void put_from_storage(std::string const &key, std::string const &value);

    tcp::socket socket_;
    io::streambuf streambuf_;
    std::string buffer_;
    std::string response_;

    std::unordered_map<std::string, std::string> &storage_;
    const static std::regex key_regex_;
    const static std::regex txt_regex_;

};

class server {

public:

    server(io::io_context& io_context, std::uint16_t port) :
        io_context_(io_context),
        acceptor_(io_context_, tcp::endpoint(tcp::v4(), port)) {
        async_accept();
    }

    void async_accept();

  private:

    io::io_context& io_context_;
    tcp::acceptor acceptor_;

    std::unordered_map<std::string, std::string> storage_;

};

#endif

server.cpp

#include "server_impl.h"

#include <boost/asio.hpp>
#include <cstddef>
#include <functional>
#include <memory>
#include <regex>
#include <sstream>
#include <string>
#include <utility>

/* session implementation */

const std::regex session::key_regex_("[A-Za-z0-9]+");
const std::regex session::txt_regex_("[A-Za-z0-9]+\\.txt");

void session::start() {
    async_read();
}

void session::async_read() {
    io::async_read_until(
        socket_,
        streambuf_,
        "\n",
        std::bind(&session::on_read, shared_from_this(), _1, _2)
    );
}

void session::on_read(error_code err, std::size_t bytes_transmitted) {
    if (!err) {
        std::stringstream message(buffer_);
        message << std::istream(&streambuf_).rdbuf();
        streambuf_.consume(bytes_transmitted);

        message_handler(buffer_);
        async_write();
        buffer_.clear();

        async_read();
    } else {
        socket_.close();
    }
}

void session::async_write() {
    io::async_write(
        socket_,
        io::buffer(response_),
        std::bind(&session::on_write, shared_from_this(), _1, _2)
    );
}

void session::on_write(error_code err, std::size_t bytes_transmitted) {
    if (!err) {
        async_write();
    } else {
        socket_.close();
    }
}

void session::message_handler(std::string const &message) {

    if (message.starts_with("PUT ")) {
        on_put(message);
    } else if (message.starts_with("GET ")) {
        on_get(message);
    } else if (message.starts_with("DEL ")) {
        on_del(message);
    } else if (message.starts_with("COUNT ")) {
        on_count(message);
    } else if (message.starts_with("DUMP ")) {
        on_dump(message);
    } else {
        response_ = "NE";
    }

}

void session::on_put(std::string const &message) {
    std::istringstream ss(message);
    std::string tmp;
    std::string key;
    int cnt {};

    ss >> tmp;
    while (ss >> tmp) {
        if (++cnt == 3) {
            response_ = "NE\n";
            return;
        }
        if (cnt == 1) {
            if (!is_valid_key(tmp)) {
                response_ = "NE\n";
                return;
            }
            key = tmp;
        }
    }

    if (cnt != 2) { 
        response_ = "NE\n";
        return;
    }

    if (std::string value = get_from_storage(key); value != "") {
        response_ = "OK " + value + "\n";
    }
    storage_[key] = tmp;
}

void session::on_get(std::string const &message) {
    std::istringstream ss(message);
    std::string tmp;
    std::string value;
    int cnt {};

    ss >> tmp;
    while (ss >> tmp) {
        if (++cnt == 2) {
            response_ = "NE\n";
            return;
        }
        if (!is_valid_key(tmp)) {
            response_ = "NE\n";
            return;
        }
        value = get_from_storage(tmp);
    }

    if (cnt != 1 || value == "") { 
        response_ = "NE\n";
        return;
    }

    response_ = "OK " + value + "\n";
}

void session::on_del(std::string const &message) {
    std::istringstream ss(message);
    std::string tmp;
    std::string value;
    int cnt {};

    ss >> tmp;
    while (ss >> tmp) {
        if (++cnt == 2) {
            response_ = "NE\n";
            return;
        }
        if (!is_valid_key(tmp)) {
            response_ = "NE\n";
            return;
        }
        value = get_from_storage(tmp);
    }

    if (cnt != 1 || value == "") { 
        response_ = "NE\n";
        return;
    }

    storage_.erase(tmp);
    response_ = "OK " + value + "\n";
}

void session::on_count(std::string const &message) {
    std::istringstream ss(message);
    std::string tmp;

    ss >> tmp;
    while (ss >> tmp) {
        response_ = "NE\n";
        return;
    }

    size_t cnt {storage_.size()};
    response_ = "OK " + std::to_string(cnt) + "\n";
}

void session::on_dump(std::string const &message) {
    std::istringstream ss(message);
    std::string tmp;
    int cnt {};

    ss >> tmp;
    while (ss >> tmp) {
        if (++cnt == 2) {
            response_ = "NE\n";
            return;
        }
    }

    if (cnt != 1 || !is_valid_txt(tmp)) { 
        response_ = "NE\n";
        return;
    }

    // dumping
    response_ = "OK\n";
}

bool session::is_valid_key(std::string const &key) {
    return std::regex_match(key, key_regex_);
}

bool session::is_valid_txt(std::string const &fname) {
    return std::regex_match(fname, txt_regex_);
}

std::string session::get_from_storage(std::string const &key) {
    if (auto it = storage_.find(key); it != storage_.end()) {
        return it->second;
    }
    return "";
}

void session::put_from_storage(std::string const &key, std::string const &value) {
    storage_[key] = value;
}


/* server implemetation */

void server::async_accept() {
    acceptor_.async_accept([this](error_code err, tcp::socket socket) {
        if (!err) {
            std::make_shared<session>(std::move(socket), storage_)->start();
        }
    });
}

启动代码

// 服务器启动代码
int main(int argc, char* argv[]) {

    if (argc < 3 || argc > 4) {
        std::cout << "usage: server <port> <limit_connections> [<dump_file>]" << std::endl;
        return 1;
    }

    io::io_context io_context;
    server srv(io_context, static_cast<uint16_t>(std::stoi(argv[1])));
    srv.async_accept();
    io_context.run();
    return 0;
}

// 客户端启动代码
int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cout << "usage: client <ip> <port>" << std::endl;
        return 1;
    }

    io::io_context io_context;
    std::make_shared<client>(io_context, "127.0.0.1", std::stoi(argv[2]));
    io_context.run();

    return 0;
}

问题根源及修复方案

1. 客户端对象生命周期失效

客户端启动代码中,std::make_shared<client>创建的对象未被持有,导致对象创建后立即销毁,socket被提前关闭,后续异步操作触发文件描述符错误。
修复: 保存客户端的shared_ptr引用:

// 客户端启动代码修改
int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cout << "usage: client <ip> <port>" << std::endl;
        return 1;
    }

    io::io_context io_context;
    auto client_ptr = std::make_shared<client>(io_context, argv[1], std::stoi(argv[2]));
    io_context.run();

    return 0;
}

2. 服务器accept循环中断

服务器async_accept仅调用一次,处理完第一个连接后停止监听新连接,后续客户端连接失败。
修复: 在accept回调中再次调用async_accept,保持监听状态:

// server.cpp中async_accept修改
void server::async_accept() {
    acceptor_.async_accept([this](error_code err, tcp::socket socket) {
        if (!err) {
            std::make_shared<session>(std::move(socket), storage_)->start();
        }
        async_accept(); // 持续监听新连接
    });
}

3. Session写操作无限递归

session::on_write中无错误时重复调用async_write,导致无限发起写操作,最终触发错误。
修复: 写完响应后启动下一次读操作,等待客户端新请求:

void session::on_write(error_code err, std::size_t bytes_transmitted) {
    if (!err) {
        response_.clear();
        async_read();
    } else {
        if (socket_.is_open()) {
            socket_.close();
        }
    }
}

4. 数据读取逻辑错误

session::on_read和client::on_read中,未正确从streambuf读取数据到缓冲区,导致后续业务逻辑处理空字符串。
修复: 用std::getline从streambuf读取完整一行:

// session::on_read修改
void session::on_read(error_code err, std::size_t bytes_transmitted) {
    if (!err) {
        std::istream is(&streambuf_);
        std::getline(is, buffer_);
        streambuf_.consume(bytes_transmitted);

        message_handler(buffer_);
        async_write();
        buffer_.clear();

        async_read();
    } else {
        if (err != boost::asio::error::eof && socket_.is_open()) {
            socket_.close();
        }
    }
}

// client::on_read修改
void client::on_read(error_code const &err, std::size_t bytes_transmitted) {
    if (!err) {
        std::istream is(&streambuf_);
        std::getline(is, response_);
        streambuf_.consume(bytes_transmitted);

        std::cout << response_ << std::endl;
        response_.clear();

        async_write();
    } else {
        if (err != boost::asio::error::eof && socket_.is_open()) {
            socket_.close();
        }
    }
}

5. 重复关闭socket

当错误为EOF时,socket已自动关闭,再次调用socket_.close()会触发"Bad file descriptor"错误。
修复: 关闭前检查socket状态,或忽略EOF错误:

// 所有调用socket_.close()的地方统一修改
if (socket_.is_open()) {
    socket_.close();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 02:28:31