boost::async_write 在写入一段时间后失败

Posted

技术标签:

【中文标题】boost::async_write 在写入一段时间后失败【英文标题】:boost::async_write fails after writing for some time 【发布时间】:2011-05-26 19:05:34 【问题描述】:

我遇到了一个非常特殊的问题。我编写了一个服务器,它将从第三方接收到的数据写入连接的客户端。服务器在一段时间内可以正常写入客户端,但一段时间后,async_write 要么失败,要么写入永远不会返回。对于我的程序,如果 async_write 永远不会返回,则不会发生后续写入,并且我的服务器将从第三方接收的数据排队,直到一切崩溃。

我在下面包含了我的代码:

void ClientPartitionServer::HandleSignal(const CommonSessionMessage& message, int transferSize) 
  boost::lock_guard<boost::mutex> lock(m_mutex);
  if(m_clientSockets.size() != 0) 
    TransferToQueueBuffer(message.GetData(), transferSize);
  
  if(m_writeCompleteFlag) 
    // TransferToWriteBuffer();
    for(vector<boost::asio::ip::tcp::socket*>::const_iterator i = m_clientSockets.begin(); i != m_clientSockets.end(); ++i) 
      WriteToClient(*i);
    
  


void ClientPartitionServer::WriteToClient(boost::asio::ip::tcp::socket* clientSocket) 
  m_writeCompleteFlag = false;
  cout << "Iniating write: " << m_identifier << endl;
  boost::asio::async_write(
    *clientSocket,
    boost::asio::buffer(m_queueBuffer.get(), m_queueBufferSize),
    boost::bind(
      &ClientPartitionServer::HandleWrite, this,
      boost::asio::placeholders::error,
      boost::asio::placeholders::bytes_transferred
  ));


void ClientPartitionServer::HandleWrite(const boost::system::error_code& ec, size_t bytes_transferred) 
  boost::lock_guard<boost::mutex> lock(m_mutex);
  if(ec != 0) 
    cerr << "Error writing to client: " << ec.message() << " " << m_identifier << endl;
    // return;
    cout << "HandleWrite Error" << endl;
    exit(0);
  
  cout << "Write complete: " << m_identifier << endl;
  m_writeCompleteFlag = true;
  m_queueBuffer.reset();
  m_queueBufferSize = 0;

任何帮助将不胜感激。

谢谢。

【问题讨论】:

具有异步 I/O 的互斥锁是 NO NO 改用 ASIO 链。有关示例和详细信息,请参阅 boost.org/doc/libs/1_46_1/doc/html/boost_asio/tutorial/…。 如果您确定 async_write 永远不会返回,那么这是 asio 中的一个错误,因为 async_write 被记录为立即返回。 【参考方案1】:

没有看到所有代码很难说,但对我来说这是一个危险信号,您在多个(甚至一个)WriteToClient 调用中持有互斥锁。通常在 I/O 上持有任何类型的锁(甚至是你这里的异步)充其量对性能不利,最坏的情况是在负载下导致奇怪的死锁。例如,如果异步写入完成内联并且您在同一线程/调用堆栈中被 HandleWrite 回调,会发生什么情况?

我会尝试重构它,以便在写调用期间释放锁。

无论解决方案如何,更一般的建议:

不要跨 I/O 锁定 添加一些诊断输出 - 什么 线程调用每个处理程序,并在 什么顺序? 点击后尝试调试 静态。应该可以 从诊断死锁 进程状态。

【讨论】:

但是,我的程序可能同时调用 async_write 代码块以及将输入写入保存要写出的数据的队列。如果我不使用互斥锁保护这些代码块,那么我应该如何防止对同一数据执行并发输入/输出导致数据损坏? +1 表示不跨 I/O 操作锁定。 @czchlong 阅读有关股线的信息,请参阅 Sean's answer。 我不认为跨 IO 持有锁是个问题。通常的问题是跨阻塞操作持有锁,以及跨回调持有锁。所以问题是 asio 是否在 async_write 中执行任何类型的回调,或者 async_write 是否可以阻塞。通过阅读文档,async_write 提供了这两个保证。它必须立即返回,并且保证不会从 async_write 中调用处理程序。【参考方案2】:

使用链来序列化对特定连接对象的访问。特别是,请查看strand::wrap()。要查看使用 strand 的其他示例,请查看 few different timer examples(尽管该代码适用于任何 async_*() 调用)。

【讨论】:

【参考方案3】:

首先,我不同意 cmets 指出在异步操作中持有锁是一个问题。

持有锁:

    任何调用回调的函数都是错误的。

    任何阻塞操作都是错误的。

async_write 明确保证既不会阻塞,也不会调用处理程序,所以持有锁对我来说看起来不错。

但是,我可以在您的代码中看到一个错误,它违反了 async_write 的另一个要求。在调用完成处理程序之前,您不能调用async_write。这就是你违反的。

只要调用了其中一个处理程序m_writeCompleteFlag 就会设置为true。这意味着您很可能在高负载下违反了其他一些 N-1 套接字的 async_write 规则。

【讨论】:

以上是关于boost::async_write 在写入一段时间后失败的主要内容,如果未能解决你的问题,请参考以下文章

boost::async_write 导致数据损坏

boost async_write:如果失败,如何跟踪未发送的内容并通知客户端/用户失败的内容?

boost async_write() 和 non_blocking socket.send() 之间的区别

发送长 TCP 段时会发生啥?

typescript 选择段时的回调

使用三个段时重写 URL 不起作用