0

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

我遇到的问题是在顺序执行大量写入时发生的,从而导致各种错误,例如 Second Chance Assertion Failed 和 Access Violation。我还收到“解密失败或坏记录 mac”的读取错误。

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

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

这是我的读/写代码的摘要:

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

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

4

1 回答 1

3

简而言之,验证所有对client::write()client::read()的调用都在strand_. 如果没有额外的代码,诸如互斥锁之类的同步结构将无法有效防止并发操作被调用。


ssl::stream用于线程安全的 Boost.Asio 文档强调了以下strand要求:

不同的物体:安全。

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

如果ssl::stream操作仅在单个线程中被调用,那么它是安全的,因为它们在隐式链中运行。另一方面,如果多个线程在一个 上调用操作ssl::stream,那么它们必须显式地与一个链同步。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.

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

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

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

于 2013-09-06T19:07:09.727 回答