简体   繁体   English

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

[英]boost::asio::ssl access violation on multi-threaded application

I've created a program that makes use of boost's ssl implementation to send and receive small packets to a remote server using async_read_some() and async_write_some() . 我创建了一个程序,该程序利用boost的ssl实现使用async_read_some()async_write_some()向远程服务器发送和接收小数据包。 The read and write are wrapped in a strand to prevent concurrent reading and writing. 读取和写入被包装成一束,以防止同时进行读取和写入。 Additionally the wrapper functions I've created for each contain a mutex to further prevent concurrent access (possibly overkill, but can't hurt). 另外,我为每个包装函数创建的包装函数都包含一个互斥体,以进一步防止并发访问(可能会造成过大杀伤力,但不会造成伤害)。 The writes are stored on a write queue where they are sent when the thread is notified that data is available. 当线程被通知有可用数据时,这些写操作将存储在写队列中,并在其中发送。

The issue I'm experiencing occurs when a large number of writes are executed sequentially, resulting in varying errors such as Second Chance Assertion Failed and Access Violation. 当顺序执行大量写入操作时,会遇到我遇到的问题,从而导致各种错误,例如第二次机会断言失败和访问冲突。 I also get a read error of "decryption failed or bad record mac". 我还收到“解密失败或错误的记录mac”的读取错误。

From the research I've done so far, I've found that SSL sockets can become corrupted if reads and writes are performed concurrently - at least according to the discussions here and here where the OP's symptoms are very similar to mine. 从到目前为止的研究中,我发现如果同时执行读写操作,SSL套接字可能会损坏-至少根据此处此处的讨论,OP的症状与我的非常相似。 He also states that the strand he was using was not functioning, but I don't understand his solution. 他还指出,他正在使用的线束未起作用,但我不理解他的解决方案。 This would make sense with the issues I'm having due to the methods I'm attempting to use to prevent concurrent reads and writes. 由于我尝试使用防止并发读取和写入的方法,这对于我遇到的问题将是有意义的。

A workaround I've been using is to throttle the sequential writes so that there's a gap of at least 20ms between each. 我一直在使用的一种解决方法是限制顺序写入,以使每次写入之间至少有20ms的间隔。 Any less than this and I start getting errors. 少于这个,我就会开始出错。 This workaround isn't great though, because at some point there may still be a concurrent read/write resulting in errors. 不过,这种解决方法并不是很好,因为在某些时候仍然可能存在并发的读/写操作,从而导致错误。

Here's a summary of my read/write code: 这是我的读/写代码的摘要:

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

I've experimented with different types of mutexes, so I don't think that is the issue. 我已经尝试了不同类型的互斥锁,所以我认为这不是问题。 If anyone knows a way to ensure my strand is doing its job or you can see some obvious errors in my code/design please let me know! 如果有人知道确保我的程序集完成工作的方法,或者您在我的代码/设计中看到一些明显的错误,请告诉我!

In short, verify that all calls to client::write() and client::read() are running within strand_ . 简而言之,请验证对client::write()client::read()所有调用是否都在strand_strand_ Without additional code, synchronization constructs, such as a mutex, will be ineffective for preventing concurrent operations from being invoked. 如果没有其他代码,同步结构(例如互斥体)将无法有效防止并发操作被调用。


The Boost.Asio documentation for ssl::stream thread safety accentuates the strand requirement: 用于ssl::stream线程安全性的Boost.Asio文档强调了strand要求:

Distinct objects: Safe. 不同的对象:安全。

Shared objects: Unsafe. 共享对象:不安全。 The application must also ensure that all asynchronous operations are performed within the same implicit or explicit strand. 应用程序还必须确保所有异步操作都在同一隐式或显式链中执行。

If the ssl::stream operations are only being invoked within a single thread, then it is safe as they are running within an implicit strand. 如果ssl::stream操作仅在单个线程中调用,那么这是安全的,因为它们在隐式链中运行。 On the other hand, if multiple threads are invoking operations on an ssl::stream , then they must be explicitly synchronized with a strand. 另一方面,如果多个线程正在ssl::stream上调用操作,则必须将它们与链显式同步。 Other synchronization constructs, such as a mutex, will be ineffective due to intermediate handlers without introducing custom asio_handler_invoke functions. 如果没有引入自定义asio_handler_invoke函数,则由于中间处理程序,其他同步结构(例如互斥体)将无效。

Here is a flow diagram showing the necessary strand usage for an asynchronous write chain: 这是显示异步写入链必要的链用法的流程图:

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(...);  -----------------------------------'
}

This requirement is the result of an implementation detail that ssl::stream asynchronous operations are implemented in terms of composed operations. 此要求是实现细节的结果,该实现细节是根据组合操作实现ssl::stream异步操作。 The initial operation may occur within the context of the caller, and thus client::write() needs to be invoked from within strand_ . 初始操作可能在调用方的上下文中发生,因此需要在strand_内调用client::write() These composed operations may result in many intermediate calls to socket_ . 这些组合操作可能导致对socket_许多中间调用。 With these intermediate calls having no knowledge of writeInProgressMutex , the mutex becomes ineffective for protecting socket_ . 由于这些中间调用不具备writeInProgressMutex知识,因此互斥体对于保护socket_无效。 Consider reading this answer for more details on composed operations and strands . 考虑阅读答案以获取有关组合操作和子strands更多详细信息。

Additionally, within client::write() , the lifetime of the buffer data_sent fails to meet requirement for ssl::stream::async_write_some() , which states: 此外,在client::write() ,缓冲区data_sent的生存期无法满足ssl::stream::async_write_some() ,该状态指出:

Although the buffers object may be copied as necessary, ownership of the underlying buffers is retained by the caller, which must guarantee that they remain valid until the handler is called. 尽管可以根据需要复制缓冲区对象,但是调用者将保留对基础缓冲区的所有权,这必须保证它们在调用处理程序之前一直保持有效。

In this case, data_sent 's lifetime ends when client::write() returns, which may occur before the completion handler has been invoked. 在这种情况下, data_sent的生存期在client::write()返回时结束,这可能在调用完成处理程序之前发生。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM