boost asio async_write:如何不交错async_write调用?

时间:2011-10-13 13:16:06

标签: c++ asynchronous boost-asio

这是我的实施:

  • 客户端A为客户端B发送消息
  • 服务器按async_read处理消息的正确数据和 将等待来自客户A的新数据(为了不阻止客户端A)
  • 之后服务器将处理信息(可能会做一个mysql 查询)然后使用async_write将消息发送给客户端B.

问题是,如果客户端A发送消息的速度非常快,async_writes将在调用前一个async_write处理程序之前进行交错。

有一种简单的方法可以避免这个问题吗?

编辑1: 如果客户端C刚刚在客户端A之后向客户端B发送消息,则应出现相同的问题...

编辑2: 这会有用吗?因为它似乎阻止了,我不知道在哪里......

 namespace structure {                                                              
  class User {                                                                     
  public:                                                                          
    User(boost::asio::io_service& io_service, boost::asio::ssl::context& context) :
      m_socket(io_service, context), m_strand(io_service), is_writing(false) {}    

    ssl_socket& getSocket() {                                                      
      return m_socket;                                                             
    }                                                                              

    boost::asio::strand getStrand() {                                              
      return m_strand;                                                             
    }                                                                              

    void push(std::string str) {                                                   
      m_strand.post(boost::bind(&structure::User::strand_push, this, str));        
    }                                                                              

    void strand_push(std::string str) {                                            

      std::cout << "pushing: " << boost::this_thread::get_id() << std::endl;       
      m_queue.push(str);                                                           
      if (!is_writing) {                                                           
        write();                                                                   
        std::cout << "going to write" << std::endl;                                
      }                                                                            
      std::cout << "Already writing" << std::endl;                                 
    }                                                                              

    void write() {                                                                 
      std::cout << "writing" << std::endl;                                         
      is_writing = true;                                                           
      std::string str = m_queue.front();                                           
      boost::asio::async_write(m_socket,                                           
                               boost::asio::buffer(str.c_str(), str.size()),       
                               boost::bind(&structure::User::sent, this)           
                               );                                                  
    }                                                                              

    void sent() {                                                                  
      std::cout << "sent" << std::endl;                                            
      m_queue.pop();                                                               
      if (!m_queue.empty()) {                                                      
        write();                                                                   
        return;                                                                    
      }                                                                            
      else                                                                         
        is_writing = false;                                                        
      std::cout << "done sent" << std::endl;                                       
    }                                          

  private:                                     
    ssl_socket          m_socket;              
    boost::asio::strand m_strand;              
    std::queue<std::string>     m_queue;       
    bool                        is_writing;    
  };                                           
}                                              

#endif

2 个答案:

答案 0 :(得分:42)

  

有一种简单的方法可以避免这个问题吗?

是,为每个客户端维护一个传出队列。检查async_write完成处理程序中的队列大小,如果非零,则启动另一个async_write操作。这是一个样本

#include <boost/asio.hpp>
#include <boost/bind.hpp>

#include <deque>
#include <iostream>
#include <string>

class Connection
{
public:
    Connection(
            boost::asio::io_service& io_service
            ) :
        _io_service( io_service ),
        _strand( _io_service ),
        _socket( _io_service ),
        _outbox()
    {

    }

    void write( 
            const std::string& message
            )
    {
        _strand.post(
                boost::bind(
                    &Connection::writeImpl,
                    this,
                    message
                    )
                );
    }

private:
    void writeImpl(
            const std::string& message
            )
    {
        _outbox.push_back( message );
        if ( _outbox.size() > 1 ) {
            // outstanding async_write
            return;
        }

        this->write();
    }

    void write()
    {
        const std::string& message = _outbox[0];
        boost::asio::async_write(
                _socket,
                boost::asio::buffer( message.c_str(), message.size() ),
                _strand.wrap(
                    boost::bind(
                        &Connection::writeHandler,
                        this,
                        boost::asio::placeholders::error,
                        boost::asio::placeholders::bytes_transferred
                        )
                    )
                );
    }

    void writeHandler(
            const boost::system::error_code& error,
            const size_t bytesTransferred
            )
    {
        _outbox.pop_front();

        if ( error ) {
            std::cerr << "could not write: " << boost::system::system_error(error).what() << std::endl;
            return;
        }

        if ( !_outbox.empty() ) {
            // more messages to send
            this->write();
        }
    }


private:
    typedef std::deque<std::string> Outbox;

private:
    boost::asio::io_service& _io_service;
    boost::asio::io_service::strand _strand;
    boost::asio::ip::tcp::socket _socket;
    Outbox _outbox;
};

int
main()
{
    boost::asio::io_service io_service;
    Connection foo( io_service );
}

一些关键点

  • boost::asio::io_service::strand保护对Connection::_outbox
  • 的访问权限
  • 处理程序从Connection::write()调度,因为它是公开的

如果您在问题的示例中使用类似的做法,那么对我来说并不明显,因为所有方法都是公开的。

答案 1 :(得分:7)

只是想改善Sam的好答案。改进点是:

  • Sound.URL = "File\Ping.mp3"在完成之前尝试从缓冲区中发送每个字节,这意味着您应该提供 您拥有的所有输入数据 < / strong>写操作,否则由于TCP数据包小于原来的数据,帧开销可能会增加。

  • async_write虽然使用起来非常方便,但不是零拷贝。下面的示例演示了 零拷贝 方法:将输入数据块保留在原来的位置,并使用asio::streambuf的分散/聚集重载来获取序列输入缓冲区(只是指向实际输入数据的指针)。

完整源代码:

async_write