多线程应用程序上的boost :: asio :: ssl访问冲突

时间:2013-09-06 11:36:28

标签: c++ ssl boost-asio

我创建了一个程序,利用boost的ssl实现,使用async_read_some()async_write_some()向远程服务器发送和接收小数据包。读取和写入包含在一个链中以防止并发读取和写入。另外,我为每个创建的包装器函数都包含一个互斥锁,以进一步防止并发访问(可能是矫枉过正,但不会受到伤害)。写入存储在写入队列中,当线程被通知数据可用时,它们将被发送。

我遇到的问题是在顺序执行大量写操作时发生,导致不同的错误,例如Second Chance Assertion Failed和Access Violation。我还得到“解密失败或错误记录mac”的读取错误。

从我迄今为止所做的研究中,我发现如果同时执行读写操作,SSL套接字可能会损坏 - 至少根据讨论herehere OP的症状与我的非常相似。他还说他正在使用的链没有运作,但我不明白他的解决方案。由于我试图用来防止并发读写的方法,这对于我所遇到的问题是有意义的。

我一直在使用的解决方法是限制顺序写入,以便每个之间至少有20ms的间隔。不到这个,我开始得到错误。但是,这种解决方法并不是很好,因为在某些时候可能仍会存在并发读/写错误。

以下是我的读/写代码摘要:

void client::write(std::string message, char packetType) {
    boost::lock_guard<boost::shared_mutex> lock(writeInProgressMutex);

    std::string preparedMessage = prepareWrite(message, packetType);
    char data_sent[2048];

    for(std::string::size_type i = 0; i < preparedMessage.size(); ++i) {
        data_sent[i] = preparedMessage[i];
        if (i + 1 == preparedMessage.size()) {
            data_sent[i+1] = NULL;
        }
    }

    socket_->async_write_some(boost::asio::buffer(data_sent), strand_.wrap(boost::bind(&client::handle_write, this, boost::asio::placeholders::error)));
}

void client::read() {
    boost::lock_guard<boost::shared_mutex> lock(readInProgressMutex);
    socket_->async_read_some(boost::asio::buffer(data_), strand_.wrap(boost::bind(&client::handle_read, this, boost::asio::placeholders::error)));
}

我已经尝试过不同类型的互斥体,所以我认为不是问题所在。如果有人知道如何确保我的钢绞线正在完成其工作,或者您可以在我的代码/设计中看到一些明显的错误,请告诉我们!

1 个答案:

答案 0 :(得分:3)

简而言之,请确认client::write()client::read()的所有来电都在strand_内。如果没有其他代码,同步构造(如互斥锁)将无法防止并发操作被调用。


ssl::stream thread safety的Boost.Asio文档强调了strand要求:

  

不同的对象:安全。

     

共享对象:不安全。应用程序还必须确保所有异步操作都在同一个隐式或显式链中执行。

如果仅在单个线程中调用ssl::stream操作,则它们在隐式链中运行时是安全的。另一方面,如果多个线程正在调用ssl::stream上的操作,则它们必须与strand明确同步。其他同步构造(例如互斥锁)由于中间处理程序而无法引入自定义asio_handler_invoke函数,因此无效。

以下是显示异步写入链的必要链使用的流程图:

void client::start_write_chain()
{
  strand_.post(boost::bind(&client::write, ...)) ---.
}    .----------------------------------------------'
     |  .-------------------------------------------.
     V  V                                           |
void client::write(...)                             |
{                                                   |
  ...                                               |
  socket_->async_write_some(buffer, strand_.wrap(   |
    boost::bind(&client::handle_write, ...)));  --. | 
}    .--------------------------------------------' |
     V                                              |
void client::handle_write(                          |
  boost::system::error_code& error,                 |
  std::size_t bytes_transferred)                    |
{                                                   |
  // If there is still data remaining.              |
  if (bytes_transferred < all_data)                 |
    write(...);  -----------------------------------'
}

此要求是实现细节的结果,ssl::stream异步操作是根据组合操作实现的。初始操作可能发生在调用者的上下文中,因此需要从client::write()内调用strand_。这些组合操作可能会导致对socket_的许多中间调用。由于这些中间调用不了解writeInProgressMutex,因此互斥锁无法保护socket_。有关撰写操作和strands的更多详细信息,请参阅this答案。

此外,在client::write()内,缓冲区data_sent的生命周期无法满足ssl::stream::async_write_some()的要求,其中指出:

  

尽管可以根据需要复制buffers对象,但调用者仍保留底层缓冲区的所有权,这必须保证它们在调用处理程序之前保持有效。

在这种情况下,data_sent的生命周期在client::write()返回时结束,这可能在调用完成处理程序之前发生。