【问题标题】:boost::asio async server designboost::asio 异步服务器设计
【发布时间】:2012-08-27 15:08:40
【问题描述】:

目前我正在使用设计,当服务器读取前 4 个字节的流,然后在标头解码后读取 N 个字节。

但我发现第一次 async_read 和第二次读取之间的时间是 3-4 毫秒。我刚刚从回调中打印了控制台时间戳以进行测量。我总共发送了 10 个字节的数据。为什么要花这么多时间阅读?

我在调试模式下运行它,但我认为调试的 1 个连接是 从套接字读取之间没有3毫秒的延迟。也许我需要 另一种在“数据包”上切割 TCP 流的方法?

更新:我在这里发布一些代码

void parseHeader(const boost::system::error_code& error)
        {
            cout<<"[parseHeader] "<<lib::GET_SERVER_TIME()<<endl;
            if (error) {
                close();
                return;
            }
            GenTCPmsg::header result = msg.parseHeader();
            if (result.error == GenTCPmsg::parse_error::__NO_ERROR__) {
                msg.setDataLength(result.size);
                boost::asio::async_read(*socket, 
                    boost::asio::buffer(msg.data(), result.size),
                    (*_strand).wrap(
                    boost::bind(&ConnectionInterface::parsePacket, shared_from_this(), boost::asio::placeholders::error)));
            } else {
                close();
            }
        }
        void parsePacket(const boost::system::error_code& error)
        {
            cout<<"[parsePacket] "<<lib::GET_SERVER_TIME()<<endl;
            if (error) {
                close();
                return;
            }
            protocol->parsePacket(msg);
            msg.flush();
            boost::asio::async_read(*socket, 
                boost::asio::buffer(msg.data(), config::HEADER_SIZE),
                (*_strand).wrap(
                boost::bind(&ConnectionInterface::parseHeader, shared_from_this(), boost::asio::placeholders::error)));
        }

如您所见,unix 时间戳相差 3-4 毫秒。我想了解为什么 parseHeader 和 parsePacket 之间要经过这么多时间。这不是客户端问题,摘要数据是 10 个字节,但我不能发送更多,延迟恰好在调用之间。我正在使用 Flash 客户端版本 11。我所做的只是通过打开的套接字发送 ByteArray。我不确定客户端的延迟。我一次发送所有 10 个字节。如何调试实际延迟在哪里?

【问题讨论】:

  • 这个问题不清楚。报价单是什么?是来自上一个问题吗?您可以编辑您的问题以包含complete reproducer?
  • 您是如何确定服务器中存在 3-4 毫秒延迟而不是由于客户端造成的?
  • @DenisErmolin 您不应该在调试模式下测量时序。性能损失可能低至 10%,或高达 10k%+
  • 代码 sn-p 并不是非常有用。我们无法编译它来重现问题,请发布完整的重现者或告诉我们what you have tried
  • 如果您在 Linux 上,“strace -f -tt -T”的输出可能值得为该应用程序发布。

标签: c++ networking boost architecture boost-asio


【解决方案1】:

有太多未知数无法从发布的代码中确定延迟的根本原因。不过,可以采取一些方法和考虑因素来帮助确定问题:

  • 为 Boost.Asio 1.47+ 启用 handler tracking。只需定义BOOST_ASIO_ENABLE_HANDLER_TRACKING,Boost.Asio 就会将调试输出(包括时间戳)写入标准错误流。这些时间戳可用于帮助过滤掉由应用程序代码(parseHeader()parsePacket() 等)引入的延迟。
  • 验证byte-ordering 是否被正确处理。例如,如果协议将标头的 size 字段定义为 network-byte-order 中的两个字节,并且服务器将该字段作为原始短格式处理,则在接收到正文大小为 10 的消息时:
    • 大端机器将调用async_read 读取10 字节。读取操作应该很快完成,因为套接字已经有 10 字节体可供读取。
    • little-endian 机器将调用async_read 读取2560 字节。读取操作可能仍处于未完成状态,因为尝试读取的字节数比预期的要多。
  • 使用straceltrace等跟踪工具。
  • 修改 Boost.Asio,在整个调用堆栈中添加时间戳。 Boost.Asio 仅作为头文件库提供。因此,用户可以对其进行修改以提供所需的详细程度。虽然不是最简洁或最简单的方法,但在整个调用堆栈中添加带有时间戳的打印语句可能有助于提供对时间的可见性。
  • 尝试在一个简短、简单、独立的示例中复制该行为。从最简单的示例开始,以确定延迟是否是系统性的。然后,对示例进行迭代扩展,使其每次迭代都更接近真实代码。

这是我开始的一个简单示例:

#include <iostream>

#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>

class tcp_server
  : public boost::enable_shared_from_this< tcp_server >
{
private:

  enum 
  {
     header_size = 4,
     data_size   = 10,
     buffer_size = 1024,
     max_stamp   = 50
  };

  typedef boost::asio::ip::tcp tcp;

public:

  typedef boost::array< boost::posix_time::ptime, max_stamp > time_stamps;

public:

  tcp_server( boost::asio::io_service& service,
              unsigned short port )
    : strand_( service ),
      acceptor_( service, tcp::endpoint( tcp::v4(), port ) ),
      socket_( service ),
      index_( 0 )
  {}

  /// @brief Returns collection of timestamps.
  time_stamps& stamps()
  {
    return stamps_;
  }

  /// @brief Start the server.
  void start()
  {
    acceptor_.async_accept( 
      socket_,
      boost::bind( &tcp_server::handle_accept, this,
                   boost::asio::placeholders::error ) );
  }

private:

  /// @brief Accept connection.
  void handle_accept( const boost::system::error_code& error ) 
  {
    if ( error )
    {  
      std::cout << error.message() << std::endl;
      return;
    }

    read_header();
  }

  /// @brief Read header.
  void read_header()
  {
    boost::asio::async_read(
      socket_,
      boost::asio::buffer( buffer_, header_size ),
      boost::bind( &tcp_server::handle_read_header, this,
                   boost::asio::placeholders::error,
                   boost::asio::placeholders::bytes_transferred ) );
  }

  /// @brief Handle reading header.
  void
  handle_read_header( const boost::system::error_code& error,
                      std::size_t bytes_transferred )
  {
    if ( error )
    {  
      std::cout << error.message() << std::endl;
      return;
    }

    // If no more stamps can be recorded, then stop the async-chain so
    // that io_service::run can return.
    if ( !record_stamp() ) return;

    // Read data.
    boost::asio::async_read(
      socket_,
      boost::asio::buffer( buffer_, data_size ),
      boost::bind( &tcp_server::handle_read_data, this,
                   boost::asio::placeholders::error,
                   boost::asio::placeholders::bytes_transferred ) );

  }

  /// @brief Handle reading data.
  void handle_read_data( const boost::system::error_code& error,
                         std::size_t bytes_transferred )
  {
    if ( error )
    {  
      std::cout << error.message() << std::endl;
      return;
    }

    // If no more stamps can be recorded, then stop the async-chain so
    // that io_service::run can return.
    if ( !record_stamp() ) return;

    // Start reading header again.
    read_header();
  }

  /// @brief Record time stamp.
  bool record_stamp()
  {
    stamps_[ index_++ ] = boost::posix_time::microsec_clock::local_time();

    return index_ < max_stamp;
  }

private:
  boost::asio::io_service::strand strand_;
  tcp::acceptor acceptor_;
  tcp::socket socket_;
  boost::array< char, buffer_size > buffer_;
  time_stamps stamps_;
  unsigned int index_;
};


int main()
{
  boost::asio::io_service service;

  // Create and start the server.
  boost::shared_ptr< tcp_server > server =
    boost::make_shared< tcp_server >( boost::ref(service ), 33333 );  
  server->start();

  // Run.  This will exit once enough time stamps have been sampled.
  service.run();

  // Iterate through the stamps.
  tcp_server::time_stamps& stamps = server->stamps();
  typedef tcp_server::time_stamps::iterator stamp_iterator;
  using boost::posix_time::time_duration;
  for ( stamp_iterator iterator = stamps.begin() + 1,
                       end      = stamps.end();
        iterator != end;
        ++iterator )
  {
     // Obtain the delta between the current stamp and the previous.
     time_duration delta = *iterator - *(iterator - 1);
     std::cout << "Delta: " << delta.total_milliseconds() << " ms"
               << std::endl;
  }
  // Calculate the total delta.
  time_duration delta = *stamps.rbegin() - *stamps.begin();
  std::cout <<    "Total" 
            << "\n  Start: " << *stamps.begin()
            << "\n  End:   " << *stamps.rbegin()
            << "\n  Delta: " << delta.total_milliseconds() << " ms"
            << std::endl;
}

关于实现的几点说明:

  • 只有一个线程(主)和一个异步链read_header->handle_read_header->handle_read_data。这应该可以最大限度地减少准备运行的处理程序等待可用线程所花费的时间。
  • 要关注boost::asio::async_read,通过以下方式最小化噪音:
    • 使用预先分配的缓冲区。
    • 不使用shared_from_this()strand::wrap
    • 记录时间戳,并在收集后执行处理。

我使用 gcc 4.4.0 和 Boost 1.50 在 CentOS 5.4 上编译。为了驱动数据,我选择使用 netcat 发送 1000 个字节:

$ ./a.out > 输出 &
[1] 18623
$ echo "$(for i in {0..1000}; do echo -n "0"; done)" |数控 127.0.0.1 33333
[1]+ 完成 ./a.out >输出
$尾输出
增量:0 毫秒
增量:0 毫秒
增量:0 毫秒
增量:0 毫秒
增量:0 毫秒
增量:0 毫秒
全部的
  开始时间:2012-9-10 21:22:45.585780
  结束:2012-9-10 21:22:45.586716
  增量:0 毫秒

观察到没有延迟,我通过修改 boost::asio::async_read 调用来扩展示例,将 this 替换为 shared_from_this() 并将 ReadHandlerss 包装为 strand_.wrap()。我运行了更新的示例,但仍然没有观察到延迟。不幸的是,根据问题中发布的代码,这是我所能得到的。

考虑扩展示例,在每次迭代中添加来自实际实现的片段。例如:

  • 首先使用msg 变量的类型来控制缓冲区。
  • 接下来,发送有效数据,并引入parseHeader()parsePacket函数。
  • 最后介绍一下lib::GET_SERVER_TIME()打印。

如果示例代码尽可能接近真实代码,并且boost::asio::async_read 没有观察到延迟,那么ReadHandlers 可能已准备好在真实代码中运行,但它们正在等待在同步(链)或资源(线程)上,导致延迟:

  • 如果延迟是与链同步的结果,请考虑Robin 的建议,通过读取更大的数据块来潜在地减少每条消息所需的读取量。
  • 如果延迟是等待线程的结果,则考虑额外调用线程io_service::run()

【讨论】:

  • @DenisErmolin:我将每个示例编译了两次:-g -O0 用于调试,-O3 -s -DNDEBUG 用于发布。
  • 经过一番研究,我发现这个问题只发生在windows平台(确切地说是win7)上,Unix平台就像一个魅力。感谢您的建议。
【解决方案2】:

让 Boost.Asio 很棒的一件事是充分利用异步功能。依赖于一批中读取的特定字节数,可能会丢弃一些已经读取的内容,这并不是您真正应该做的。

相反,请查看网络服务器的示例,尤其是这个:http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/example/http/server/connection.cpp

boost tribolean 用于 a) 如果所有数据在一个批次中都可用,则完成请求,b) 如果它可用但无效,则放弃它,以及 c) 如果请求不完整,则在 io_service 选择时读取更多信息.连接对象通过共享指针与处理程序共享。

为什么这优于大多数其他方法?您可以节省已经解析请求的读取之间的时间。遗憾的是,示例中没有遵循这一点,但理想情况下,您应该对处理程序进行线程化,以便它可以处理已经可用的数据,而将其余数据添加到缓冲区中。它阻塞的唯一时间是数据不完整时。

希望这会有所帮助,但无法解释为什么读取之间会有 3 毫秒的延迟。

【讨论】:

    猜你喜欢
    • 2014-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-02
    • 2015-08-07
    • 1970-01-01
    相关资源
    最近更新 更多