使用asynch boost :: asio和多个客户端连接

时间:2016-08-09 21:09:18

标签: c++ boost visual-studio-2013 tcp boost-asio

我需要建立最多三个到不同服务器的不同TCP连接。所有三个连接都需要不同的协议,不同的握手和不同的心跳。学习http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/example/cpp11/chat/chat_client.cpp,阅读这里的内容并遵循Chris Kohlhoffs的建议,我试着按照下面的方式实现它。

问题在于,使用这种架构,无论我在做什么,我都会在doConnect()中调用shared_from_this()时收到bad_weak_pointer异常。

Importent 这些只是未运行代码的代码段,可能包含错误!的 Importent

我有一个包含一些基本方法的基类。

Connection.h

class Connection : public std::enable_shared_from_this<Connection>
{
public:
  //! Ctor
  inline Connection();
  //! Dtor
  inline virtual ~Connection();
  inline void setReconnectTime(const long &reconnectAfterMilisec)
  {
    m_reconnectTime = boost::posix_time::milliseconds(reconnectAfterMilisec);
  }
  inline void setHandshakePeriod(const long &periodInMilisec)
  {
    m_handshakePeriod = boost::posix_time::milliseconds(periodInMilisec);
  }
  virtual void doConnect() = 0;
  virtual void stop() = 0;
  //... and some view more...
}

然后我有三个派生自基类的类。这里只描述了一种方法(也是核心部分)。

ConnectionA.h

//queues which containing also the age of the messages
typedef std::deque<std::pair<handshakeMsg, boost::posix_time::ptime>> handskMsg_queue;
typedef std::deque<std::pair<errorcodeMsg, boost::posix_time::ptime>> ecMsg_queue;
typedef std::deque<std::pair<A_Msg, boost::posix_time::ptime>> A_Msg_queue;

class ConnectionA : public Connection
{
public:
  ConnectionA();
  ConnectionA(const std::string& IP, const int &port);
  ConnectionA& operator=(const ConnectionA &other);
  virtual ~ConnectionA();
  virtual void stop() override;
  virtual void doConnect() override;
  void doPost(std::string &message);
  void doHandshake();
  void sendErrorCode(const int &ec);

  std::shared_ptr<boost::asio::io_service>m_ioS;

private:
  std::shared_ptr<tcp::socket> m_socket;
  std::shared_ptr<boost::asio::deadline_timer> m_deadlineTimer; // for reconnetions
  std::shared_ptr<boost::asio::deadline_timer> m_handshakeTimer; // for heartbeats

  void deadlineTimer_handler(const boost::system::error_code& error);
  void handshakeTimer_handler(const boost::system::error_code& error);
  void doRead();
  void doWrite();

  std::string m_IP;
  int m_port;
  handskMsg_queue m_handskMsgQueue;
  ecMsg_queue m_ecMsgQueue;
  A_Msg_queue m_AMsgQueue;
}

ConnectionA.cpp

ConnectionA::ConnectionA(const std::string &IP, const int &port)
: m_ioS()
, m_socket()
, m_deadlineTimer()
, m_handshakeTimer()
, m_IP(IP)
, m_port(port)
, m_handskMsgQueue(10)
, m_ecMsgQueue(10)
, m_AMsgQueue(10)   
{
  m_ioS = std::make_shared<boost::asio::io_service>();
  m_socket = std::make_shared<tcp::socket>(*m_ioS);
  m_deadlineTimer = std::make_shared<boost::asio::deadline_timer>(*m_ioS);
  m_handshakeTimer = std::make_shared<boost::asio::deadline_timer> (*m_ioS);
  m_deadlineTimer->async_wait(boost::bind(&ConnectionA::deadlineTimer_handler, this, boost::asio::placeholders::error));
  m_handshakeTimer->async_wait(boost::bind(&ConnectionA::handshakeTimer_handler, this, boost::asio::placeholders::error));
}
ConnectionA::~ConnectionA()
{}

void ConnectionA::stop()
{
  m_ioS->post([this]() { m_socket->close(); });
  m_deadlineTimer->cancel();
  m_handshakeTimer->cancel();
}

void ConnectionA::doConnect()
{
  if (m_socket->is_open()){
    return;
  }
  tcp::resolver resolver(*m_ioS);
  std::string portAsString = std::to_string(m_port);
  auto endpoint_iter = resolver.resolve({ m_IP.c_str(), portAsString.c_str() });
  m_deadlineTimer->expires_from_now(m_reconnectTime);
  // this gives me a bad_weak_pointer exception!!!
  auto self = std::static_pointer_cast<ConnectionA>(static_cast<ConnectionA*>(this)->shared_from_this()); 
  boost::asio::async_connect(*m_socket, endpoint_iter, [this, self](boost::system::error_code ec, tcp::resolver::iterator){
    if (!ec)
    {
      doHandshake();
      doRead();
    } 
    else {
      // don't know if async_connect can fail but set the socket to open
      if (m_socket->is_open()){
        m_socket->close();
      }
    }
  });
}

void ConnectionA::doRead()
{
  auto self(shared_from_this());
  boost::asio::async_read(*m_socket, 
    boost::asio::buffer(m_readBuf, m_readBufSize), 
    [this, self](boost::system::error_code ec, std::size_t){
    if(!ec){
        // check server answer for errors
        }
        doRead();
    }
    else {
        stop();
    }
  });
}

void ConnectionA::doPost(std::string &message)
{
    A_Msg newMsg (message);
    auto self(shared_from_this());
    m_ioS->post([this, self, newMsg](){
    bool writeInProgress = false;
    if (!m_A_MsgQueue.empty()){
        writeInProgress = true;
    }
    boost::posix_time::ptime currentTime = time_traits_t::now();
    m_AMsgQueue.push_back(std::make_pair(newMsg,currentTime));
    if (!writeInProgress)
    { 
        doWrite();
    }       
  });   
}

void ConnectionA::doWrite()
{
  while (!m_AMsgQueue.empty())
  {
    if (m_AMsgQueue.front().second + m_maxMsgAge < time_traits_t::now()){
            m_AMsgQueue.pop_front();
            continue;
    }
    if (!m_socket->is_open()){
        continue;
    }
    auto self(shared_from_this());
    boost::asio::async_write(*m_socket,
      boost::asio::buffer(m_AMsgQueue.front().first.data(),
      m_AMsgQueue.front().first.A_lenght),
      [this, self](boost::system::error_code ec, std::size_t /*length*/)
    {
        if (!ec) // successful
      {
        m_handshakeTimer->expires_from_now(m_handshakePeriod); // reset timer
        m_AMsgQueue.pop_front();
        doWrite();
      }
      else {
        if (m_socket->is_open()){
          m_socket->close();
        }
      }
    });
  }
} 

void ConnectionA::deadlineTimer_handler(const boost::system::error_code& error){
  if (m_stopped){
    return;
  }
  m_deadlineTimer->async_wait(boost::bind(&ConnectionA::deadlineTimer_handler, this, boost::asio::placeholders::error));
  if (!error && !m_socket->is_open()) // timer expired and no connection was established
  {     
    doConnect();
  }
  else if (!error && m_socket->is_open()){ // timer expired and connection was established
    m_deadlineTimer->expires_at(boost::posix_time::pos_infin); // to reactivate timer call doConnect()
  }
}

最后还有另一个类封装了这些类,使其使用起来更加舒适:

TcpConnect.h

class CTcpConnect
{
public:
  /*! Ctor
  */
  CTcpConnect();
  //! Dtor
  ~CTcpConnect();

  void initConnectionA(std::string &IP, const int &port);
  void initConnectionB(std::string &IP, const int &port);
  void initConnectionC(std::string &IP, const int &port);

  void postMessageA(std::string &message);

  void run();
  void stop();
private:
  ConnectionA m_AConnection;
  ConnectionB m_BConnection;
  ConnectionC m_CConnection;
}

TcpConnect.cpp

CTcpConnect::CTcpConnect()
: m_AConnection()
, m_BConnection()
, m_CConnection()
{}

CTcpConnect::~CTcpConnect()
{}

void CTcpConnect::run(){
  [this](){ m_AConnection.m_ioS->run(); };
  [this](){ m_BConnection.m_ioS->run(); };
  [this](){ m_CConnection.m_ioS->run(); };
}

void CTcpConnect::stop(){
  m_AConnection.stop();
  m_BConnection.stop();
  m_CConnection.stop();
}

void CTcpConnect::initConnectionA(std::string &IP, const int &port)
{
  m_AConnection = ConnectionA(IP, port);
  m_AConnection.setMaxMsgAge(30000);
  //... set some view parameter more
  m_AConnection.doConnect();
}
// initConnectionB & initConnectionC are quite the same

void CTcpConnect::postMessageA(std::string &message)
{
  m_AConnection.doWrite(message);
}

一开始我也尝试过只有一个io_service(对于我的方法,这样会很好),但是保持服务就像引用一样令人头疼,因为我的实现也需要一个默认的连接构造函数。现在每个连接都有自己的io-service。

我是如何才能运行此代码的? 随意为其他架构提出建议。如果你能提出这个,那么一些片段甚至会更好。几周以来,我一直在努力实施这项工作。我很感激每一个提示。

BTW我在VS12上使用了boost 1.61。

1 个答案:

答案 0 :(得分:1)

这是问题所在:

m_AConnection = ConnectionA(IP, port);

也就是说,ConnectionA派生自Connection,派生自enable_shared_from_this。这意味着必须将ConnectionA实例化为shared_from_this的共享指针才能工作。

试试这个:

void CTcpConnect::initConnectionA(std::string &IP, const int &port)
{
  m_AConnection = std::make_shared<ConnectionA>(IP, port);
  m_AConnection->setMaxMsgAge(30000);
  //... set some view parameter more
  m_AConnection->doConnect();
}

EDIT1:

  

你是对的。那就是问题所在。现在我意识到我调用io-service.run()的方式是完全废话。

使用多个io_service非常罕见,每个连接使用一个非常罕见:)

  

但是,你知道我是否需要演员然后调用shared_from_this()?我注意到asynch_connect()在使用和不使用强制转换时都能正常工作。

为方便起见,许多Asio示例使用shared_from_this(),例如我根本不在我的项目中使用它。在使用Asio时,您需要注意某些规则。例如,一个是在执行相应的回调之前不得破坏读写缓冲区,如果lambda函数捕获一个指向保存缓冲区的对象的共享指针,则这个条件很简单。

你也可以这样做:

auto data = std::make_shared<std::vector<uint8_t>>(10);
async_read(socket,
           boost::asio::const_buffer(*data),
           [data](boost::system::error_code, size_t) {});

这将是有效的,但会有性能上的缺点,即每次读取时都会在std::vector内分配新数据。

当您查看一些lambda时,可以看到shared_from_this()有用的另一个原因,它们通常具有以下形式:

[this, self,...](...) {...}

也就是说,您经常要在其中使用this。如果你也没有捕获self,那么你需要使用其他措施来确保在调用处理程序时没有销毁this