2017-09-25 266 views
0

我正在實現一個使用boost::asio實現TLS連接庫的類。boost :: asio io_service :: run_one導致分段錯誤

我只實現同步操作,其中一些接受超時我實現使用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 修正了評論中提到的錯誤。

回答

0

我用指針代替了所有的成員屬性,現在它可以工作(也就是說,我可以通過我寫的所有測試)。該方法斷開/連接現在如下:

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; 
    } 
} 

正如你可以看到,現在socket,該io_servicedeadline_timercontext是在連接建立和斷開釋放。我還是不明白是怎麼回事,讓我解釋一下:

我試圖重新實現上述變量一個挨着一個,那就是,首先socket,那麼timer,那麼context,最後io_service

只有當io_service是ptr時,測試才通過,但我不明白爲什麼。如果io_service是一個類範圍的變量,那麼每當類實例超出範圍時就應該刪除它,也就是說,每當我的TEST完成時。

看來,在實施它作爲一個ptr之前,這並沒有發生。我懷疑這可能是因爲readNBytes由於超時而引發異常時,read_async調用將保留在io_service操作隊列中,並且可能導致此問題。

1

你搞亂了指針和對象生存期管理。如果connect方法在已連接時調用,則用new覆蓋舊套接字,然後檢查它是否已連接或在某處使用。另外auto_ptr已棄用。您應該使用unique_ptr來管理擁有指針。

+0

我可以'現在既不使用C++ 11也不使用智能ptrs,但是我意識到我的原始ptrs比'auto_ptr' – Dan