C++ 如何在动态销毁boost::asio实体时消除崩溃?

C++ 如何在动态销毁boost::asio实体时消除崩溃?,c++,boost,boost-asio,asio,C++,Boost,Boost Asio,Asio,注意!!!这个问题针对的是boost::asio库方面的专家。不幸的是,我不能做的代码更紧凑,它包含了一个最低数额的描述问题。代码是人工创建的示例。在已知并在评论中描述了其崩溃的地方,它旨在说明崩溃无需调试代码的任何帮助 问题是如何设计asio服务器,而不是它在哪里崩溃 这个例子接近于官方boost::asio文档中的“聊天服务器”设计。但是,与官方示例不同,官方示例仅动态创建/销毁连接类的对象,在我的示例中,服务器及其连接类实体都是动态创建/销毁的。。。我相信这种模式的实现应该在asio爱好者

注意!!!这个问题针对的是
boost::asio库
方面的专家。不幸的是,我不能做的代码更紧凑,它包含了一个最低数额的描述问题。代码是人工创建的示例。在已知并在评论中描述了其崩溃的地方,它旨在说明崩溃<代码>无需调试代码的任何帮助

问题是如何设计asio服务器,而不是它在哪里崩溃

这个例子接近于官方boost::asio文档中的“聊天服务器”设计。但是,与官方示例不同,官方示例仅动态创建/销毁连接类的对象,在我的示例中,服务器及其连接类实体都是动态创建/销毁的。。。我相信这种模式的实现应该在asio爱好者中广为人知,下面描述的问题应该已经有人解决了

请看代码。 在这里,CAsioServer和CAsioConnection的实体是动态创建和销毁的

#include <map>
#include <array>
#include <set>
#include <vector>
#include <deque>
#include <thread>
#include <iostream>
#include <asio.hpp>
#include <iomanip>


class CAsioConnection
    : public std::enable_shared_from_this<CAsioConnection>
{
public:
    using PtrType = std::shared_ptr<CAsioConnection>;

    CAsioConnection(asio::ip::tcp::socket socket, std::set<CAsioConnection::PtrType>& connections)
        : socket_(std::move(socket)), connections_(connections)
    {
        std::cout << "-- CAsioConnection is creating, socket: " << socket_.native_handle() << "\n";
    }

    virtual ~CAsioConnection()
    {
        std::cout << "-- CAsioConnection is destroying , socket: " << socket_.native_handle() << "\n";
    }

    void read() { do_read(); }

private:
    void do_read(void)
    {
        uint8_t buff[3];

        asio::async_read(socket_, asio::buffer(buff,3),
            [this](std::error_code ec, std::size_t  /*length*/) {
            if (!ec)
            {
                do_read();
            }
            else
            {
                std::cout << "-- CAsioConnection::do_read() error : " << ec.message() << "\n";
                // Here is the crash N2
                connections_.erase(shared_from_this());
                // Crash may be fixed by the code below
                //if (ec.value() != 1236) // (winerror.h) #define ERROR_CONNECTION_ABORTED 1236L
                //  connections_.erase(shared_from_this());
            }
        });
    }

    asio::ip::tcp::socket socket_;
    std::set<CAsioConnection::PtrType>& connections_;
};

class CAsioServer
    : public std::enable_shared_from_this<CAsioServer>
{
public:
    using PtrType = std::shared_ptr<CAsioServer>;

    CAsioServer(int port, asio::io_context& io, const asio::ip::tcp::endpoint& endpoint)
        : port_(port), acceptor_(io, endpoint)
    {
        std::cout << "-- CAsioServer is creating, port: " << port_ << "\n";
    }

    virtual ~CAsioServer()
    {
        std::cout << "-- CAsioServer is destroying , port: " << port_ << "\n";
    }

    int port(void) { return port_; }

    void accept(void) { do_accept(); }
private:
    void do_accept()
    {
        acceptor_.async_accept([this](std::error_code ec, asio::ip::tcp::socket socket) {
            if (!ec)
            {
                std::cout << "-- CAsioServer::do_accept() connection to socket: " << socket.native_handle() << "\n";
                auto c = std::make_shared<CAsioConnection>(std::move(socket), connections_);
                connections_.insert(c);
                c->read();
            }
            else
            {
                // Here is the crash N1
                std::cout << "-- CAsioServer::do_accept() error : " << ec.message() << "\n";
                // Crash may be fixed by the code below
                //if (ec.value() == 995) // (winerror.h) #define ERROR_OPERATION_ABORTED 995L
                //  return;
            }
            // Actually here is the crash N1 )), but the fix is above...
            do_accept();
        });
    }

    int port_;
    asio::ip::tcp::acceptor acceptor_;
    std::set<CAsioConnection::PtrType> connections_;
};

//*****************************************************************************

class CTcpBase
{
public:
    CTcpBase()
    {
        // heart beat timer to keep it alive
        do_heart_beat();
        t_ = std::thread([this] {
            std::cout << "-- io context is RUNNING!!!\n";
            io_.run();
            std::cout << "-- io context has been STOPED!!!\n";
        });
    }

    virtual ~CTcpBase()
    {
        io_.stop();

        if (t_.joinable())
            t_.join();
    }

    void add_server(int port)
    {
        io_.post([this, port] 
        {
            for (auto s : servers_)
                if (port == s->port())
                    return;

            auto endpoint = asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port);
            auto s = std::make_shared<CAsioServer>(port, io_, endpoint);
            s->accept();
            servers_.insert(s);
        });
    }

    void remove_server(int port)
    {
        io_.post([this, port] 
        {
            for (auto s : servers_)
                if (port == s->port())
                    { servers_.erase(s); return; }
        });
    }

private:

    void do_heart_beat(void)
    {
        std::cout << "-- beat\n";
        auto timer = std::make_shared<asio::steady_timer>(io_, asio::chrono::milliseconds(3000));
        timer->async_wait([timer, this](const asio::error_code& ec) {
            do_heart_beat();
        });
    }

    asio::io_context io_;
    std::thread t_;
    std::set<CAsioServer::PtrType> servers_;
};

//*****************************************************************************

int main(void)
{
    CTcpBase tcp_base;

    std::cout << "CONNECT the server to port 502\n";
    tcp_base.add_server(502);

    std::this_thread::sleep_for(std::chrono::seconds(20));
    
    std::cout << "REMOVE the server from port 502\n";
    tcp_base.remove_server(502);

    std::this_thread::sleep_for(std::chrono::seconds(10));

    return 0;
}
#包括
#包括
  • 启动应用程序。通过任何外部客户端连接到端口502,并等待不到20秒。 崩溃发生在
    CAsioConnection::do_read()
    中,请参见下面的输出。
  • asio框架似乎在其类的实体已销毁时调用了延迟的
    asio::async\u read()
    acceptor.async\u accept()
    处理程序

    我已经用错误检查修复了处理程序,但解决方案似乎不可靠。谁知道还会有什么其他错误和情况…有时,当客户端断开连接时,我需要清除
    asio::async\u read()
    上的
    连接设置,如何确保服务器或连接对象仍处于活动状态


    有没有办法要求boost::asio framework防止调用已销毁对象的延迟处理程序?或者如何通过错误代码识别对象已被销毁的
    (100%确定)
    ?或者,在asio范围内可能还有其他解决方案或设计模式-如何在一个运行线程中处理恶意创建/破坏的服务器及其连接,而不使用互斥锁和其他东西…

    首先检查您的
    io_服务是否严格运行单线程。这在代码中不可见。如果不是,则共享状态(如
    连接
    )需要同步访问

    事实上,您可以有一个接受循环形式的逻辑串,但要利用这一点,您应该使对
    连接的所有访问都发生在那里,请参见例如

    • 在这里,会话列表直接保存会话,根本不需要共享指针:
    • 或者在这里,我们有共享指针,并在会话列表中存储弱指针,这些指针可以从接受循环内部“垃圾收集”:
    更新
    • buff
      是一个局部变量,它会导致未定义的行为,因为它在异步读取操作的整个时间内都无效

    • 一般来说,从这个
    习惯用法中共享
    是没有意义的,而且还保留一个共享指针容器,它已经决定了生命周期

    您的问题似乎是有时
    CAsioServer
    会被简单地销毁,这意味着
    connections\uu
    的所有元素都会被释放,此时它们的
    CAsioConnection
    对象可能会被销毁。它还将销毁
    CAsioServer

    每当Asio对象被销毁时,任何挂起的异步操作都将失败,并出现
    Asio::error:operation\u aborted
    ,这确实意味着您已做出响应。但是,当调用完成处理程序时,对象已经无效

    在中,我刚刚注意到缺少一个关键成分:您从未在任何完成处理程序中捕获/绑定指向
    CAsioConnection
    的共享指针

    这是非常不习惯的

    相反,您使用共享指针来管理生命周期。如果您还需要一个连接列表,那么将其设置为一个弱指针列表,以便它只观察生命周期

    变化点:

    • 无需使服务器从此\u启用\u共享\u

    • 连接
      应该包含弱指针,甚至是非拥有指针。这里的弱点显然要安全得多。事实上,您可以选择放弃该容器,因为似乎没有任何东西在使用它。在下面的示例中,我选择保留它,以便您可以看到它的实际效果

    • 在完成处理程序中从此
    捕获
    shared\u,以确保对象在激发时仍然有效:

     asio::async_read(socket_, asio::buffer(buff,3),
         [this, self=shared_from_this()](error_code ec, std::size_t  /*length*/) {
    
    简化 注意我选择了
    std::list
    ,因为它消除了平等/排序的需要(请参见
    std::owner_less
    ),这变得越来越难看,因为在
    CAsioConnection
    类中存储对容器的引用的方式使其循环依赖(在实例化
    owner\less
    类之前,
    CAsioConnection
    类型尚未完成)。我只是选择了(不需要的)复杂性

    #include <boost/asio.hpp>
    #include <iostream>
    #include <list>
    #include <memory>
    
    namespace asio = boost::asio;
    using error_code = boost::system::error_code; // compat
    
    class CAsioConnection : public std::enable_shared_from_this<CAsioConnection> {
      public:
        using PtrType = std::shared_ptr<CAsioConnection>;
    
        CAsioConnection(asio::ip::tcp::socket socket) : socket_(std::move(socket)) {
            log(__FUNCTION__);
        }
    
        ~CAsioConnection() { log(__FUNCTION__); }
    
        void read() { do_read(); }
    
      private:
        void log(std::string_view msg) const {
            error_code ec;
            std::clog << msg << ", socket: " << socket_.remote_endpoint(ec) << "\n";
        }
    
        uint8_t buff[256];
        void do_read() {
            asio::async_read(socket_, asio::buffer(buff),
                 [this, self = shared_from_this()](error_code ec, std::size_t length) {
                     if (!ec) {
                         log(__FUNCTION__ + (" length: " + std::to_string(length)));
                         do_read();
                     } else {
                         log(__FUNCTION__ + (" error: " + ec.message()));
                     }
                 });
        }
    
        asio::ip::tcp::socket socket_;
    };
    
    class CAsioServer {
      public:
        CAsioServer(asio::io_context& io, const asio::ip::tcp::endpoint& endpoint)
                : acceptor_(io, endpoint) { log(__FUNCTION__); }
    
        ~CAsioServer() { log(__FUNCTION__); }
        int port() const { return acceptor_.local_endpoint().port(); }
        void accept() { do_accept(); }
    
      private:
        void do_accept() {
            acceptor_.async_accept([this](error_code ec,
                                          asio::ip::tcp::socket socket) {
                if (!ec) {
                    auto c = std::make_shared<CAsioConnection>(std::move(socket));
                    connections_.push_back(c);
                    c->read();
                } else {
                    log(__FUNCTION__ + (" error: " + ec.message()));
                }
    
                connections_.remove_if(std::mem_fn(&WeakPtr::expired));
    
                if (acceptor_.is_open())
                    do_accept();
            });
        }
    
        void log(std::string_view msg) const {
            std::clog << msg << ", port: " << port() << "\n";
        }
    
        asio::ip::tcp::acceptor acceptor_;
        using WeakPtr = std::weak_ptr<CAsioConnection>;
        std::list<WeakPtr> connections_;
    };
    
    int main() {
        boost::asio::io_context io;
    
        CAsioServer server(io, { {}, 7878 });
        server.accept();
    
        io.run_for(std::chrono::seconds(10));
    }
    
    #包括
    #包括
    #包括
    #包括
    名称空间asio=boost::asio;
    使用error\u code=boost::system::error\u code;//compat
    类CAsioConnection:public std::从\u启用\u共享\u{
    公众:
    使用PtrType=std::shared\u ptr;
    CAsioConnection(asio::ip::tcp::socket-socket):socket(std::move(socket)){
    日志(函数);
    }
    ~CAsioConnection(){log(_函数__);}
    void read(){do_read();}
    私人:
    无效日志(std::string\u view msg)常量{
    错误代码ec;
    我亲爱的华生,木屐小学
    问题的关键是——我非常信任你
    
    ./a.out& sleep 1; nc -w 1 127.0.0.1 7878 < main.cpp
    CAsioServer, port: 7878
    CAsioConnection, socket: 127.0.0.1:50628
    operator() length: 256, socket: 127.0.0.1:50628
    operator() length: 256, socket: 127.0.0.1:50628
    operator() length: 256, socket: 127.0.0.1:50628
    operator() length: 256, socket: 127.0.0.1:50628
    operator() length: 256, socket: 127.0.0.1:50628
    operator() length: 256, socket: 127.0.0.1:50628
    operator() length: 256, socket: 127.0.0.1:50628
    operator() length: 256, socket: 127.0.0.1:50628
    operator() length: 256, socket: 127.0.0.1:50628
    operator() error: End of file, socket: 127.0.0.1:50628
    ~CAsioConnection, socket: 127.0.0.1:50628
    ~CAsioServer, port: 7878
    
    #include <map>
    #include <array>
    #include <set>
    #include <vector>
    #include <deque>
    #include <thread>
    #include <iostream>
    #include <asio.hpp>
    #include <iomanip>
    
    
    class CAsioConnection
        : public std::enable_shared_from_this<CAsioConnection>
    {
    public:
        using PtrType = std::shared_ptr<CAsioConnection>;
    
        CAsioConnection(asio::ip::tcp::socket socket, std::set<CAsioConnection::PtrType>& connections)
            : socket_(std::move(socket)), connections_(connections), destroying_in_progress(false)
        {
            std::cout << "-- CAsioConnection is creating\n";
        }
    
        virtual ~CAsioConnection()
        {
            std::cout << "-- CAsioConnection is destroying\n";
        }
    
        void read() { do_read(); }
    
        void hasta_la_vista(void)
        {
            destroying_in_progress = true;
            std::error_code ec;
            socket_.cancel(ec);
        }
    
    private:
        void do_read(void)
        {
            auto self(shared_from_this());
            asio::async_read(socket_, asio::buffer(buff),
                [this, self](std::error_code ec, std::size_t  /*length*/) {
    
                if (destroying_in_progress)
                    return;
    
                if (!ec)
                {
                    do_read();
                }
                else
                {
                    std::cout << "-- CAsioConnection::do_read() error : (" << ec.value() << ") " << ec.message() << "\n";
                    hasta_la_vista();
                    connections_.erase(shared_from_this());
                }
            });
        }
    
        uint8_t buff[3];
        asio::ip::tcp::socket socket_;
        bool destroying_in_progress;
        std::set<CAsioConnection::PtrType>& connections_;
    };
    
    //*****************************************************************************
    
    class CAsioServer
        : public std::enable_shared_from_this<CAsioServer>
    {
    public:
        using PtrType = std::shared_ptr<CAsioServer>;
    
        CAsioServer(int port, asio::io_context& io, const asio::ip::tcp::endpoint& endpoint)
            : port_(port), destroying_in_progress(false), acceptor_(io, endpoint)
        {
            std::cout << "-- CAsioServer is creating, port: " << port_ << "\n";
        }
    
        virtual ~CAsioServer()
        {
            for (auto c : connections_)
            {
                c->hasta_la_vista();
            }
    
            std::cout << "-- CAsioServer is destroying , port: " << port_ << "\n";
        }
    
        int port(void) { return port_; }
    
        void accept(void) { do_accept(); }
        void hasta_la_vista(void) 
        { 
            destroying_in_progress = true;
            std::error_code ec;
            acceptor_.cancel(ec);
        }
    private:
        void do_accept()
        {
            auto self(shared_from_this());
            acceptor_.async_accept([this, self](std::error_code ec, asio::ip::tcp::socket socket) {
    
                if (destroying_in_progress)
                    return;
    
                if (!ec)
                {
                    std::cout << "-- CAsioServer::do_accept() connection to socket: " << socket.native_handle() << "\n";
                    auto c = std::make_shared<CAsioConnection>(std::move(socket), connections_);
                    connections_.insert(c);
                    c->read();
                }
                else
                {
                    std::cout << "-- CAsioServer::do_accept() error : (" << ec.value() << ") "<<  ec.message() << "\n";
                }
                do_accept();
            });
        }
    
        int port_;
        bool destroying_in_progress;
        asio::ip::tcp::acceptor acceptor_;
        std::set<CAsioConnection::PtrType> connections_;
    };
    
    //*****************************************************************************
    
    class CTcpBase
    {
    public:
        CTcpBase()
        {
            // heart beat timer to keep it alive
            do_heart_beat();
            t_ = std::thread([this] {
                std::cout << "-- io context is RUNNING!!!\n";
                io_.run();
                std::cout << "-- io context has been STOPED!!!\n";
            });
        }
    
        virtual ~CTcpBase()
        {
            io_.stop();
    
            if (t_.joinable())
                t_.join();
        }
    
        void add_server(int port)
        {
            io_.post([this, port] {
                for (auto& s : servers_)
                    if (port == s->port())
                        return;
    
                auto endpoint = asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port);
                auto s = std::make_shared<CAsioServer>(port, io_, endpoint);
                s->accept();
                servers_.insert(s);
            });
        }
    
        void remove_server(int port)
        {
            io_.post([this, port] {
                for (auto s : servers_)
                    if (port == s->port())
                    { 
                        s->hasta_la_vista();
                        servers_.erase(s); 
                        return; 
                    }
            });
        }
    
    private:
    
        void do_heart_beat(void)
        {
            std::cout << "-- beat\n";
            auto timer = std::make_shared<asio::steady_timer>(io_, asio::chrono::milliseconds(3000));
            timer->async_wait([timer, this](const std::error_code& ec) {
                do_heart_beat();
            });
        }
    
        asio::io_context io_;
        std::thread t_;
        std::set<CAsioServer::PtrType> servers_;
    };
    
    //*****************************************************************************
    
    int main(void)
    {
        CTcpBase tcp_base;
    
        std::cout << "CONNECT the server to port 502\n";
        tcp_base.add_server(502);
    
        std::this_thread::sleep_for(std::chrono::seconds(20));
    
        std::cout << "REMOVE the server from port 502\n";
        tcp_base.remove_server(502);
    
        std::this_thread::sleep_for(std::chrono::seconds(10));
        
        return 0;
    }