我创建了一个程序,利用boost的ssl实现,使用async_read_some()
和async_write_some()
向远程服务器发送和接收小数据包。读取和写入包含在一个链中以防止并发读取和写入。另外,我为每个创建的包装器函数都包含一个互斥锁,以进一步防止并发访问(可能是矫枉过正,但不会受到伤害)。写入存储在写入队列中,当线程被通知数据可用时,它们将被发送。
我遇到的问题是在顺序执行大量写操作时发生,导致不同的错误,例如Second Chance Assertion Failed和Access Violation。我还得到“解密失败或错误记录mac”的读取错误。
从我迄今为止所做的研究中,我发现如果同时执行读写操作,SSL套接字可能会损坏 - 至少根据讨论here和here 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)));
}
我已经尝试过不同类型的互斥体,所以我认为不是问题所在。如果有人知道如何确保我的钢绞线正在完成其工作,或者您可以在我的代码/设计中看到一些明显的错误,请告诉我们!
答案 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()
返回时结束,这可能在调用完成处理程序之前发生。