boost :: asio异步服务器设计

时间:2012-09-02 17:53:01

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

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

但我发现第一次async_read和第二次读取之间的时间是3-4 ms。我刚从回调中打印出控制台时间戳进行测量。我总共发送了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个字节。我如何调试实际延迟的位置?

2 个答案:

答案 0 :(得分:25)

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

  • 为Boost.Asio 1.47+启用{{​​3}}。只需定义BOOST_ASIO_ENABLE_HANDLER_TRACKING和Boost.Asio就会将调试输出(包括时间戳)写入标准错误流。这些时间戳可用于帮助过滤应用程序代码(parseHeader()parsePacket()等)引入的延迟。
  • 验证是否正确处理了handler tracking。例如,如果协议将标头的size字段定义为网络字节顺序中的两个字节,并且服务器将该字段作为原始短路处理,则在收到主体大小为{{1 }}:
    • 大端机器将调用10读取async_read个字节。由于套接字已经有10字节体可供读取,因此读操作应该很快完成。
    • little-endian机器将调用10读取async_read个字节。读取操作可能仍然未完成,因为尝试读取的字节数远远超过预期。
  • 使用跟踪工具,例如byte-orderingstrace
  • 修改Boost.Asio,在整个callstack中添加时间戳。 Boost.Asio作为头文件库提供。因此,用户可以修改它以提供所需的冗长程度。虽然不是最干净或最简单的方法,但在整个callstack中添加带有时间戳的print语句可能有助于提供对时间的可见性。
  • 尝试在简短,自包含的示例中复制行为。从最简单的示例开始,确定延迟是否为systamtic。然后,迭代地扩展示例,使其在每次迭代时变得更接近实际代码。

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

2560

关于实施的一些注意事项:

  • 只有一个线程(主)和一个异步链 read_header-&gt; handle_read_header-&gt; handle_read_data 。这应该最小化准备运行的处理程序等待可用线程所花费的时间。
  • 要关注#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; } ,噪音最小化:
    • 使用预先分配的缓冲区。
    • 未使用boost::asio::async_readshared_from_this()
    • 记录时间戳,并在收集后执行处理。

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

$ ./a.out > output &
[1] 18623
$ echo "$(for i in {0..1000}; do echo -n "0"; done)" | nc 127.0.0.1 33333
[1]+  Done                    ./a.out >output
$ tail output
Delta: 0 ms
Delta: 0 ms
Delta: 0 ms
Delta: 0 ms
Delta: 0 ms
Delta: 0 ms
Total
  Start: 2012-Sep-10 21:22:45.585780
  End:   2012-Sep-10 21:22:45.586716
  Delta: 0 ms

观察没有延迟,我通过修改strand::wrap调用,用boost::asio::async_read替换this并用shared_from_this()包裹ReadHandlers来扩展示例。我运行了更新的示例,仍然没有发现任何延迟。不幸的是,根据问题中发布的代码,这是我可以得到的。

考虑扩展示例,在每次迭代时从实际实现中添加一个部分。例如:

  • 首先使用strand_.wrap()变量的类型来控制缓冲区。
  • 接下来,发送有效数据,并引入msgparseHeader()函数。
  • 最后,介绍parsePacket print。

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

  • 如果延迟是与strand同步的结果,那么可以通过读取更大的数据块来考虑netcat的建议,以减少每条消息所需的读取量。
  • 如果延迟是等待线程的结果,则考虑进行额外的线程调用ReadHandler

答案 1 :(得分:5)

使Boost.Asio真棒的一件事就是充分利用异步功能。依赖于一批读取的特定字节数,可能会丢弃一些已经读过的内容,实际上并不是你应该做的。

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

提升triboolean用于a)如果所有数据在一个批次中可用,则完成请求; b)如果可用但无效则抛弃它; c)当io_service选择请求不完整时读取更多。连接对象通过共享指针与处理程序共享。

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

希望这有所帮助,但不能说明为什么读取之间有3ms的延迟。