ASIO chat session class throws exception on destruction: C++ ASIO

有些话、适合烂在心里 提交于 2019-12-11 11:52:16

问题


I am working on a Network Application using ASIO and have referred Chat-Server/Client

I have asked similar question Here

To explain better I am adding more code here:

My Cserver Class

class CServer {
private:

    mutable tcp::acceptor acceptor_; // only in the listener
    asio::io_service& io_;
    CSerSessionsManager mng_;

    std::string ip_;
    std::string port_;

public:

    CServer::CServer(asio::io_service& io_service, const std::string IP, const std::string port) : io_(io_service), acceptor_(io_service)
        , ip_(IP), port_(port)
    {
        DEBUG_MSG("Listener Created");
    }

    ~CServer()
    {
        DEBUG_MSG("Listener Destroyed");
        acceptor_.close();
    }

    void initProtocol()
    {
        DEBUG_MSG(" Protocol Initiated");
        std::array<unsigned char, 4> ip;
        std::string delimiter = ".";

        //Parse the IP String
        size_t pos = 0;
        auto i = 0;
        std::string token;

        while ((pos = ip_.find(delimiter)) != std::string::npos) {
            token = ip_.substr(0, pos);
            ip[i] = std::stoi(token);//what if stoi fails
            i++;
            ip_.erase(0, pos + delimiter.length());
        }

        ip[i] = std::stoi(ip_);


        asio::ip::address_v4 address(ip);
        tcp::endpoint ep(address, std::stoi(port_));


        static std::mutex m;
        std::unique_lock<std::mutex> lck(m, std::defer_lock);

        //Critical Section start
        lck.lock();
        acceptor_ = tcp::acceptor(io_, ep);//Creating IOService
        lck.unlock();
        //Critical Section End

        listen();
    }


    void listen()
    {
        DEBUG_MSG("!==============================================================!");

        ////Critical Section
        static std::mutex m;
        std::lock_guard<std::mutex> lock(m);

        sessionPtr newSession = std::make_shared<CSerSession>(io_, mng_);

        try
        {
            acceptor_.async_accept(newSession->socket(), std::bind(&CServer::handle_accept, /*shared_from_this()*/ this, newSession,
                std::placeholders::_1));
            ///*asio::error_code ec;
            //pSocket_->shutdown(asio::ip::tcp::socket::shutdown_send, ec);*/
        }
        catch (const std::bad_weak_ptr& e)
        {
            DEBUG_MSG(e.what());
            throw e;
        }

        DEBUG_MSG("Listen Activated");
    }


    void handle_accept(sessionPtr newSession, const asio::error_code& error)
    {
        if (!acceptor_.is_open())
        {
            return;
        }

        if (!error)
        {
            DEBUG_MSG("Incoming Session accepted");
            //Do I need a Lock here?
            //Critical Section
            static std::mutex m;
            std::lock_guard<std::mutex> lock(m);
            newSession->startSession();
            listen();
        }
        else
        {
            DEBUG_MSG("Listen_Error");
            //  //throw ASIOError(Listen_Error);
            DEBUG_MSG(error.message());
            return;
        }
    }

};

My CSerSessionsManager Class

class CSerSessionsManager{
private:

    std::set<sessionPtr> sessions_; //Active Sessions : Online Info

public:
    CSerSessionsManager();
    ~CSerSessionsManager();

    void addSession(sessionPtr session);
    void dropSession(sessionPtr session);
    };
CSerSessionsManager::CSerSessionsManager()
{
    DEBUG_MSG("Construction");
}

CSerSessionsManager::~CSerSessionsManager()
{
    DEBUG_MSG("Destruction");
}

void CSerSessionsManager::addSession(sessionPtr session)
{
    DEBUG_MSG("Incoming Session Entry saved");
    //Critical Section
    static std::mutex m;
    std::lock_guard<std::mutex> lock(m);
    sessions_.insert(session);
}

void CSerSessionsManager::dropSession(sessionPtr session)
{
    //Properly handle Existing connections first shutdown sockets
    DEBUG_MSG("Session dropped");

    //Critical Section
    static std::mutex m;
    std::lock_guard<std::mutex> lock(m);

    std::set<sessionPtr>::iterator it;
    for (it = sessions_.begin(); it != sessions_.end(); ++it)
    {
        if ((*it) == session)
        {
            sessions_.erase(session);
            return;
        }
    }
    //throw ASIOError(Session_Not_Found);
}

And my CSerSession Class

class CSerSession : public std::enable_shared_from_this < CSerSession > {
private:

    mutable tcp::socket socket_;   // client connection
    CSerSessionsManager& manager_;

    std::string ip_;
    std::string port_;

    CBuffer msg_;

public:
    CSerSession(asio::io_service& io_service, CSerSessionsManager& mng) :
        manager_(mng), socket_(io_service)
    {
        DEBUG_MSG("Server Session Created");
    }

    ~CSerSession()
    {
        DEBUG_MSG("Server Session Destroyed");
    }

    void startSession()
    {
        DEBUG_MSG("Server Session Started");
        //Critical Section
        static std::mutex m;
        std::lock_guard<std::mutex> lock(m);
        manager_.addSession(shared_from_this());//Multiple threads should not try adding section
        read(msg_);
    }

    void handle_read(const asio::error_code& error /*error*/, size_t bytes_transferred /*bytes_transferred*/)
    {
        if (!error)
        {
            DEBUG_MSG("Read");
            //Critical Section
            static std::mutex m;
            std::lock_guard<std::mutex> lock(m);

            read(msg_);
        }
        else
        {
            DEBUG_MSG("Read Error Detected : " << error.message());
            //Check If shared_from_this() is valid or not
            try
            {
                //Check if session was already dropped e.g. server object destroying
                //i.e. if session object exists
                DEBUG_MSG("Dropping Session");
                //if (error == asio::error::operation_aborted)
                manager_.dropSession(shared_from_this());
            }
            catch (const std::bad_weak_ptr& e)
            {
                DEBUG_MSG(e.what());
                throw e;
            }
            return;
        }
    }

    void read(CBuffer & buff)
    {
        DEBUG_MSG("Read");
        asio::async_read(socket_, asio::buffer(const_cast<char *> (buff.getReceived()), buff.buffsize),
            std::bind(&CSerSession::handle_read, shared_from_this(),
            std::placeholders::_1, std::placeholders::_2));
    }


    tcp::socket& socket()
    {
        //Critical Section
        static std::mutex m;
        std::lock_guard<std::mutex> lock(m);
        return socket_;
    }
};

I create the CServer Object in main as below:

void main()
{
    try
    {
        asio::io_service io_service;
        //CServer  server(io_service, "Default", "127.0.0.1", "8000");
        auto sPtr = std::make_shared<CServer>(io_service, "127.0.0.1", "8000");
        sPtr->initProtocol();
        //server.initProtocol();
        asio::thread t(boost::bind(&asio::io_service::run, &io_service));

    }
    catch (...)
    {
    }

    system("Pause");
}

The Output Log I get as below:

CSerSessionsManager::CSerSessionsManager                                   :  183  :    Construction
CServer::CServer                                                           :  239  :    Listener Created
CServer::initProtocol                                                      :  250  :     Protocol Initiated
CServer::listen                                                            :  288  :    !==============================================================!
CSerSession::CSerSession                                                   :  108  :    Server Session Created
CServer::listen                                                            :  309  :    Listen Activated
CServer::~CServer                                                          :  244  :    Listener Destroyed
CSerSessionsManager::~CSerSessionsManager                                  :  188  :    Destruction
CSerSession::~CSerSession                                                  :  113  :    Server Session Destroyed

When CServer Object destroys associated CSerSession Object also destroys , so while returning from ~CSerSession() It throws exception boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<std::system_error> > at memory location 0x0277F19C. at below lines of code:

#ifndef BOOST_EXCEPTION_DISABLE
    throw enable_current_exception(enable_error_info(e));
#else
    throw e;
#endif
}

I tried to debug a lot and tried using signal mechanism also as discussed in HTTP Server, but I am stuck here and not able to proceed further.

The complete code can be checked here: MyCode

How do I resolve it?


回答1:


From a fixed version of the linked code: Live On Coliru I get

CSerSessionsManager   :  184  :    Construction
CServer               :  240  :    Listener Created
initProtocol          :  251  :     Protocol Initiated
~CServer              :  245  :    Listener Destroyed
~CSerSessionsManager  :  189  :    Destruction

NOTE: this was because I already had something listening on port 8000 (yay for error reporting!)

Did the initialization order of the fields fix it? Or is there something not running at all on my system (because of a race condition on my faster machine?).

Looks like the latter becuase on Coliru I got

CSerSessionsManager    :  184  :    Construction
CServer                :  240  :    Listener Created
initProtocol           :  251  :     Protocol Initiated
listen                 :  289  :    !===================================!
CSerSession            :  109  :    Server Session Created
listen                 :  310  :    Listen Activated
~CServer               :  245  :    Listener Destroyed
~CSerSessionsManager   :  189  :    Destruction
~CSerSession           :  114  :    Server Session Destroyed

So, let's have a closer look:

  1. why are you parsing the IP string? That's what address_v4 is for. And ip::tcp::resolver.

    DEBUG_MSG(" Protocol Initiated");
    asio::ip::address_v4 address = asio::ip::address_v4::from_string(ip_);
    tcp::endpoint ep(address, std::stoi(port_));
    
  2. using a static mutex is rarely useful. Did you mean to synchronize access to shared resources? Then you need a shared mutex too

  3. why are you using defer-lock? Use scopes

    {
        //Critical Section start
        std::lock_guard<std::mutex> lck(mutex_);
        acceptor_ = tcp::acceptor(io_, ep);//Creating IOService
        //Critical Section End
    }
    
  4. the main thread just exits, never joining the io thread. At least join. Or make it properly shutdown before terminating the program:

    t.join();
    
  5. hungarian naming is really useless here. sPtr doesn't tell me anything. server or, if you insist, server_ptr is what you need to know.

  6. you have out-of-bounds write here:

    received_[str.size()] = '\0';
    

    you wanted

    received_[len] = '\0';
    
  7. your empty doesn't need to loop

    bool empty() const
    {
        return !received_[0];
    }
    
  8. why are you looping to find stuff in an ordered set?

    std::set<sessionPtr>::iterator it;
    for (it = sessions_.begin(); it != sessions_.end(); ++it)
    {
        if ((*it) == session)
        {
            sessions_.erase(session);
            return;
        }
    }
    

    should be

    sessions_.erase(session);
    
  9. addSession/dropSession are internally locking; you don't need to put access to them in a critical section

  10. throw e is an antipattern; just throw; is re-throw

  11. you have redundant tracing almost everywhere (this is what debuggers are for). E.g. DEBUG_MSG("Read")

  12. Locking here is bogus:

    tcp::socket& socket()
    {
        // Critical Section
        std::lock_guard<std::mutex> lock(mutex_);
        return socket_;
    }
    

    The reference returned will not be protected anyways, and socket is only initialized once.

  13. all the thread locking seems redundant since there is only one service thread

  14. CBuffer msg is a bogus parameter to read() as the same buffer is passed always. This could be plenty ok (it's in the same session), so, just use it.

  15. this

        acceptor_ = tcp::acceptor(io_, ep);
    

    should be

        acceptor_.bind(ep);
    

    and not in a critical section (server is only created once); Hence the initProtocol function can be

    void initProtocol()
    {
        acceptor_.bind(tcp::endpoint(asio::ip::address_v4::from_string(ip_), std::stoi(port_)));
        listen();
    }
    
  16. in listen you're catching bad_weak_ptr which can't even occur

  17. here:

    //Do I need a Lock here?
    //Critical Section
    std::lock_guard<std::mutex> lock(mutex_);
    newSession->startSession();
    

    you don't need the lock. newSession was bound from a local variable. It's impossible for it to be shared unless you copied the completion handler (you didn't).

Here's a more fixed up version:

Live On Coliru

#include <iostream>
#include <boost/asio.hpp>
#include <memory>
#include <deque>
#include <set>
#include <iomanip>
#include <mutex>
#include <boost/bind.hpp>
#include <boost/thread.hpp>

#define DEBUG ON

#ifdef DEBUG
#define DEBUG_MSG(str) do {std::cout << std::setw(75) << std::left  << __FUNCTION__ \
    << std::setw(3) << std::left << ":" << std::setw(5) << std::left << __LINE__ \
    << std::setw(5) << std::left << ":"\
    << std::left  << str \
    << std::endl;} while( false )
#else
#define DEBUG_MSG(str) do { } while ( false )
#endif

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

class CSerSession;
using sessionPtr = std::shared_ptr<CSerSession>;

class CSerSessionsManager {
  private:
    mutable std::mutex mutex_;
    std::set<sessionPtr> sessions_; // Active Sessions : Online Info

  public:
    CSerSessionsManager();
    ~CSerSessionsManager();

    void addSession(sessionPtr session);
    void dropSession(sessionPtr session);
};

class CBuffer {

  public:
    enum { buffsize = 32 };

  private:
    char received_[buffsize];

  public:
    CBuffer() : received_{} {}

    CBuffer(const std::string str)
    {
        // Truncate if Overflow
        auto len = str.size();
        if (len >= buffsize) {
            len = buffsize - 1;
        }
        std::copy(str.begin(), str.begin() + len, received_);
        received_[len] = '\0';
    }

    bool empty() const
    {
        return !received_[0];
    }
    const std::string getString() const { return std::string(received_); }
    const char* getReceived() const { return received_; }
};

class CSerSession : public std::enable_shared_from_this<CSerSession> {
  private:
    mutable std::mutex mutex_;
    mutable tcp::socket socket_; // client connection
    CSerSessionsManager& manager_;

    std::string ip_;
    std::string port_;

    CBuffer msg_;

  public:
    CSerSession(asio::io_service& io_service, CSerSessionsManager& mng) : socket_(io_service), manager_(mng)
    {
        DEBUG_MSG("Server Session Created");
    }

    ~CSerSession() { DEBUG_MSG("Server Session Destroyed"); }

    void startSession()
    {
        DEBUG_MSG("Server Session Started");
        manager_.addSession(shared_from_this()); // Multiple threads should not try adding section

        read();
    }

    tcp::socket& socket() { return socket_; }
  private:
    void handle_read(const boost::system::error_code& error /*error*/, size_t /*bytes_transferred*/)
    {
        if (!error) {
            read();
        } else {
            DEBUG_MSG("Read Error Detected : " << error.message());
            manager_.dropSession(shared_from_this()); // might throw
        }
    }

    void read()
    {
        std::lock_guard<std::mutex> lock(mutex_);
        DEBUG_MSG("Read");
        asio::async_read(socket_, asio::buffer(const_cast<char*>(msg_.getReceived()), msg_.buffsize),
                         std::bind(&CSerSession::handle_read, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
    }
};


CSerSessionsManager::CSerSessionsManager()
{
    DEBUG_MSG("Construction");
}

CSerSessionsManager::~CSerSessionsManager()
{
    DEBUG_MSG("Destruction");
}

void CSerSessionsManager::addSession(sessionPtr session)
{
    std::lock_guard<std::mutex> lock(mutex_);
    DEBUG_MSG("Incoming Session Entry saved");
    sessions_.insert(session);
}

void CSerSessionsManager::dropSession(sessionPtr session)
{
    std::lock_guard<std::mutex> lock(mutex_);
    DEBUG_MSG("Session dropped");
    sessions_.erase(session);
}

class CServer {
private:
    mutable std::mutex mutex_;
    asio::io_service& io_;
    mutable tcp::acceptor acceptor_; // only in the listener
    CSerSessionsManager mng_;
public:

    CServer(asio::io_service& io_service, const std::string& IP, int port)
        : io_(io_service), acceptor_(io_, tcp::endpoint(asio::ip::address::from_string(IP), port))
    {
        DEBUG_MSG("Listener Created");
    }

    ~CServer()
    {
        DEBUG_MSG("Listener Destroyed");
        acceptor_.close(); // likely to be redundant
    }

    void initProtocol()
    {
        listen();
    }

  private:
    void listen()
    {
        DEBUG_MSG("!==============================================================!");

        sessionPtr newSession = std::make_shared<CSerSession>(io_, mng_);

        std::lock_guard<std::mutex> lock(mutex_);
        acceptor_.async_accept(newSession->socket(), std::bind(&CServer::handle_accept, this, newSession,
                    std::placeholders::_1));
    }

    void handle_accept(sessionPtr newSession, const boost::system::error_code& error)
    {
        if (error || !acceptor_.is_open()) {
            DEBUG_MSG("Listen_Error");
            DEBUG_MSG(error.message());
            return;
        }

        DEBUG_MSG("Incoming Session accepted");
        newSession->startSession();
        listen();
    }
};

int main()
{
    try
    {
        asio::io_service io_service;
        auto server = std::make_shared<CServer>(io_service, "127.0.0.1", 8973);
        server->initProtocol();
        boost::thread t(boost::bind(&asio::io_service::run, &io_service));

        boost::this_thread::sleep_for(boost::chrono::seconds(3));

        t.join();
    }
    catch (...)
    {
    }
}

Prints (for a single connection):

CSerSessionsManager :  123  :    Construction
CServer             :  156  :    Listener Created
listen              :  173  :    !==============================================================!
CSerSession         :  86   :    Server Session Created
handle_accept       :  190  :    Incoming Session accepted
startSession        :  93   :    Server Session Started
addSession          :  134  :    Incoming Session Entry saved
read                :  114  :    Read
listen              :  173  :    !==============================================================!
CSerSession         :  86   :    Server Session Created
handle_read         :  106  :    Read Error Detected : End of file
dropSession         :  141  :    Session dropped
~CSerSession        :  89   :    Server Session Destroyed


来源:https://stackoverflow.com/questions/28564029/asio-chat-session-class-throws-exception-on-destruction-c-asio

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!