boost :: asio io_service :: run_one会导致分段错误

时间:2017-09-25 11:17:10

标签: c++ ssl boost boost-asio

我正在实现一个使用boost::asio来实现TLS连接库的类。

我只实现同步操作,其中一些接受超时我使用deadline_timer和io_service.run_one 实现超时方法,如本例中所述:http://www.boost.org/doc/libs/1_45_0/doc/html/boost_asio/example/timeouts/async_tcp_client.cpp

我的问题在于一种能够准确读取' n'来自套接字的字节,并接受超时作为参数。问题是io_service.run_one()正在提升SIGSEV,我不知道为什么。下面是代码(它太长了,但我不知道还有其他更好的解释方法):

代码

以下是我正在执行的测试中涉及的方法:

void CMDRboostConnection::check_deadline()
{
  // Check whether the deadline has passed. We compare the deadline against
  // the current time since a new asynchronous operation may have moved the
  // deadline before this actor had a chance to run.
  if (m_timeoutOpsTimer->expires_at() <= boost::asio::deadline_timer::traits_type::now())
  {
    // TODO do I need to cancel async operations?
    m_timeoutOpsErrorCode = boost::asio::error::timed_out;

   // There is no longer an active deadline. The expiry is set to positive
   // infinity so that the actor takes no action until a new deadline is set.
   m_timeoutOpsTimer->expires_at(boost::posix_time::pos_infin);
  }

  // Put the actor back to sleep.
  m_timeoutOpsTimer->async_wait(
      boost::bind(&CMDRboostConnection::check_deadline, this));
}

bool CMDRboostConnection::connect()
{
  // TODO: This method already throws an exception, it should be void.
  DEBUG("Connecting to " + m_url + " : " + m_port);
  try
  {
    // If the socket is already connected, disconnect it before
    // opening a new conneciont.
    if (isConnected())
    {
      disconnect();
    }

    m_socket = new SSLSocket(m_ioService, m_context);

    tcp::resolver resolver(m_ioService);
    tcp::resolver::query query(m_url, m_port);

    tcp::resolver::iterator end;
    tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);

    boost::asio::connect(m_socket->lowest_layer(), resolver.resolve(query));

    if (endpoint_iterator == end)
    {
      DEBUG("Endpoint cannot be resolved, disconnecting...");
      disconnect();
    }
    else
    {
      m_timeoutOpsTimer = new boost::asio::deadline_timer(m_ioService);
      m_timeoutOpsTimer->expires_at(boost::posix_time::pos_infin);
      // Start the persistent actor that checks for deadline expiry.
      check_deadline();

      DEBUG("Endpoint resolved, performing handshake");
      m_socket->set_verify_mode(boost::asio::ssl::verify_none);
      m_socket->handshake(SSLSocket::client);

      DEBUG("Handshake done, connected to " + m_url + " : " + m_port);
      m_isConnected = true;
    }
  }
  catch (boost::system::system_error &err)
  {
    disconnect();
    throw;
  }

  return m_isConnected;
}

std::streambuf& CMDRboostConnection::readNBytes(int n, unsigned int timeout)
{
  try
  {
    if(!isConnected())
    {
      std::string err = "Cannot read, not connected";
      ERROR(err);
      throw std::logic_error(err);
    }

    if(n == 0)
    {
      return m_buffer;
    }

    m_timeoutOpsTimer->expires_from_now(
        boost::posix_time::milliseconds(timeout));

    m_timeoutOpsErrorCode = boost::asio::error::would_block;

    boost::asio::async_read(
        *m_socket,
        m_buffer,
        boost::asio::transfer_exactly(n),
        boost::bind(
            &CMDRboostConnection::timoutOpsCallback,
            this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred)
    );

    do
    {
      m_ioService.run_one();
    } while (m_timeoutOpsErrorCode == boost::asio::error::would_block);

    if(m_timeoutOpsErrorCode)
    {
      throw boost::system::system_error(m_timeoutOpsErrorCode);
    }

    return m_buffer;
  }
  catch(boost::system::system_error &err)
  {
    ERROR("Timeout reached trying to read a message");
    disconnect();
    throw;
  }
}

void CMDRboostConnection::disconnect()
{
  try
  {
    DEBUG("Disconnecting...");
    if(isConnected())
    {
      m_socket->shutdown();

      DEBUG("Closing socket...");
      m_socket->lowest_layer().close();

      if(m_socket != NULL)
      {
        delete m_socket;
        m_socket = NULL;
      }
    }

    if(m_timeoutOpsTimer != NULL)
    {
      delete m_timeoutOpsTimer;
      m_timeoutOpsTimer = NULL;
    }

    DEBUG("Disconnection performed properly");
    m_isConnected = false;
  }
  catch (boost::system::system_error &err)
  {
    ERROR("Exception thrown, error = " << err.code() <<
        ", category: " << err.code().category().name() << std::endl);

    m_isConnected = false;

    throw;
  }
}

测试

下面是我正在运行的测试方法:

 TEST(CMDRboostConnection, readNbytesTimeoutDoesNotMakeTheProgramCrashWhenTmeout)
{
  std::auto_ptr<CMDR::SSL::ICMDRsslConnection> m_connection =
          std::auto_ptr<CMDR::SSL::ICMDRsslConnection>(
              new CMDR::SSL::CMDRboostConnection("localhost", "9999"));

  unsigned int sleepInterval = 0; // seconds
  unsigned int timeout = 10; // milliseconds
  unsigned int numIterations = 10;
  std::string msg("delay 500000"); // microseconds

  if(!m_connection->isConnected())
  {
    m_connection->connect();
  }


  for(unsigned int i = 0; i < numIterations; i++)
  {

    if(!m_connection->isConnected())
    {
      m_connection->connect();
    }

    ASSERT_NO_THROW( m_connection->write(msg) );

    ASSERT_THROW (
        m_connection->readNBytes(msg.size(), timeout),
        boost::system::system_error);

    ASSERT_FALSE(m_connection->isConnected());

    ASSERT_NO_THROW( m_connection->connect() );

    sleep(sleepInterval);
  }
}

问题

在上面的测试中,第一次循环迭代正常,也就是说,第一次调用方法readNBytes时,它可以工作(按预期引发异常)。第二次执行时,它会引发SIGSEV

修改

我正在执行上述测试以测试其他功能。我已经意识到如果我只执行上面的测试,它就可以了。但是,如果我另外执行它,那么程序会与提到的SIGSEV崩溃。

这是导致问题的测试之一:

TEST(CMDRboostConnection, canConnectDisconnect)
{
  std::auto_ptr<CMDR::SSL::ICMDRsslConnection> m_connection =
          std::auto_ptr<CMDR::SSL::ICMDRsslConnection>(
              new CMDR::SSL::CMDRboostConnection("localhost", "9999"));

  unsigned int sleepInterval = 0; // seconds
  unsigned int timeout = 1000; // milliseconds
  unsigned int numIterations = 10;
  std::string msg("normally");


  if(!m_connection->isConnected())
  {
    ASSERT_NO_THROW (m_connection->connect() );
  }

  for(unsigned int i = 0; i < numIterations; i++)
  {
    ASSERT_NO_THROW( m_connection->disconnect() );
    sleep(sleepInterval);
    ASSERT_NO_THROW( m_connection->connect() );
  }
}

总之,如果我执行上述两个测试,第一个测试就会崩溃。但是如果我只执行第一个,那就可以了。

编辑2 修复了评论中提到的错误。

2 个答案:

答案 0 :(得分:1)

你搞砸了指针和对象生命周期管理。如果在已连接时调用connect方法,则用new覆盖旧套接字,然后检查它是否已在某处连接或使用。此外,auto_ptr也已弃用。您应该使用unique_ptr来管理拥有指针。

答案 1 :(得分:0)

我已经用指针替换了所有成员属性,现在它可以工作(也就是说,我可以通过我写的所有测试)。现在,disconnect / connect方法如下:

bool CMDRboostConnection::connect()
{
  // TODO: This method already throws an exception, it should be void.
  DEBUG("Connecting to " + m_url + " : " + m_port);
  try
  {
    // If the socket is already connected, disconnect it before
    // opening a new conneciont.
    if (isConnected())
    {
      disconnect();
    }

    m_ioService = new boost::asio::io_service();
    m_timeoutOpsTimer = new boost::asio::deadline_timer(*m_ioService);
    m_context = new boost::asio::ssl::context(boost::asio::ssl::context::sslv23);
    m_socket = new SSLSocket(*m_ioService, *m_context);

    tcp::resolver resolver(*m_ioService);
    tcp::resolver::query query(m_url, m_port);

    tcp::resolver::iterator end;
    tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);

    boost::asio::connect(m_socket->lowest_layer(), resolver.resolve(query));

    if (endpoint_iterator == end)
    {
      DEBUG("Endpoint cannot be resolved, disconnecting...");
      disconnect();
    }
    else
    {
      m_timeoutOpsTimer->expires_at(boost::posix_time::pos_infin);
      // Start the persistent actor that checks for deadline expiry.
      check_deadline();

      DEBUG("Endpoint resolved, performing handshake");
      m_socket->set_verify_mode(boost::asio::ssl::verify_none);
      m_socket->handshake(SSLSocket::client);

      DEBUG("Handshake done, connected to " + m_url + " : " + m_port);
      m_isConnected = true;
    }
  }
  catch (boost::system::system_error &err)
  {
    disconnect();
    throw;
  }

  return m_isConnected;
}

void CMDRboostConnection::disconnect()
{
  try
  {
    DEBUG("Disconnecting...");
    if(isConnected())
    {
      m_socket->shutdown();

      DEBUG("Closing socket...");
      m_socket->lowest_layer().close();

      if(m_socket != NULL)
      {
        delete m_socket;
        m_socket = NULL;
      }
    }

    if(m_timeoutOpsTimer != NULL)
    {
      delete m_timeoutOpsTimer;
      m_timeoutOpsTimer = NULL;
    }

    if(m_context != NULL)
    {
      delete m_context;
      m_context = NULL;
    }

    if(m_ioService != NULL)
    {
      delete m_ioService;
      m_ioService = NULL;
    }

    DEBUG("Disconnection performed properly");
    m_isConnected = false;
  }
  catch (boost::system::system_error &err)
  {
    ERROR("Exception thrown, error = " << err.code() <<
        ", category: " << err.code().category().name() << std::endl);

    if(m_timeoutOpsTimer != NULL)
    {
      delete m_timeoutOpsTimer;
      m_timeoutOpsTimer = NULL;
    }

    if(m_context != NULL)
    {
      delete m_context;
      m_context = NULL;
    }

    if(m_ioService != NULL)
    {
      delete m_ioService;
      m_ioService = NULL;
    }

    m_isConnected = false;

    throw;
  }
}

如您所见,现在socketio_servicedeadline_timercontext在连接时创建,并在断开连接时释放。我仍然不明白发生了什么,让我解释一下:

我尝试重新实现上一个变量,即首先是socket,然后是timer,然后是context,最后是io_service

仅当io_service是ptr时,测试才通过,但我不明白为什么。如果io_service是一个类范围的变量,那么每次类实例超出范围时都应删除它,也就是说,每次我的一个TEST完成时都应删除它。

在将其作为ptr实现之前,似乎没有发生这种情况。我怀疑,当readNBytes由于超时而引发异常时,read_async调用仍然在io_service操作队列中,这可能会导致问题。