14

現在、サーバーがストリームの最初の 4 バイトを読み取り、ヘッダーのデコード後に N バイトを読み取るときに設計を使用しています。

しかし、最初の async_read と 2 番目の読み取りの間の時間は 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 バイトですが、それ以上送信することはできません。遅延は正確に呼び出しの間にあります。私はフラッシュ クライアント バージョン 11 を使用しています。クライアントの遅延はわかりません。一度に 10 バイトすべてを送信します。実際の遅延がある場所をデバッグするにはどうすればよいですか?

4

2 に答える 2

26

投稿されたコードから遅延の根本原因を特定するには、不明な点が多すぎます。それにもかかわらず、問題を特定するのに役立ついくつかのアプローチと考慮事項があります。

  • Boost.Asio 1.47+ のハンドラー追跡を有効にします。定義するだけBOOST_ASIO_ENABLE_HANDLER_TRACKINGで、Boost.Asio はタイムスタンプを含むデバッグ出力を標準エラー ストリームに書き込みます。parseHeader()これらのタイムスタンプは、アプリケーション コード ( 、parsePacket()など)によって生じる遅延を除外するために使用できます。
  • バイト順が適切に処理されていることを確認します。たとえば、プロトコルがヘッダーのsizeフィールドをネットワーク バイト順で 2 バイトとして定義し、サーバーがフィールドを raw short として処理している場合、ボディ サイズが のメッセージを受信すると、次のようになります10
    • ビッグ エンディアン マシンは、async_read読み取り10バイトを呼び出します。10ソケットには読み取り可能なバイト本体がすでにあるため、読み取り操作はすぐに完了するはずです。
    • リトルエンディアン マシンは、async_readreading 2560bytes を呼び出します。意図したよりもはるかに多くのバイトが読み取られようとしているため、読み取り操作は未解決のままになる可能性があります。
  • straceltraceなどのトレース ツールを使用します。
  • Boost.Asio を変更して、コールスタック全体にタイムスタンプを追加します。Boost.Asio は、ヘッダー ファイルのみのライブラリとして出荷されます。したがって、ユーザーはそれを変更して、必要なだけ詳細を提供できます。最もクリーンで簡単なアプローチではありませんが、コールスタック全体にタイムスタンプを含む print ステートメントを追加すると、タイミングを可視化するのに役立つ場合があります。
  • 短い、単純な自己完結型の例で動作を複製してみてください。最も単純な例から始めて、遅延が系統的かどうかを判断します。次に、反復ごとに実際のコードに近づくように、例を繰り返し拡張します。

これが私が始めた簡単な例です:

#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;
}

実装に関するいくつかの注意事項:

  • 1 つのスレッド (メイン) と 1 つの非同期チェーン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)" | NC 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呼び出しを変更し、 を に置き換えthisshared_from_this()でラップしReadHandlersて、例を拡張しましたstrand_.wrap()。更新された例を実行しましたが、それでも遅延は見られませんでした。残念ながら、それは質問に投稿されたコードに基づいて取得できる限りです。

反復ごとに実際の実装の一部を追加して、例を拡張することを検討してください。例えば:

  • msg変数の型を使用してバッファを制御することから始めます。
  • 次に、有効なデータを送信し、関数を導入parseHeader()parsePacketます。
  • 最後にlib::GET_SERVER_TIME()プリントを紹介します。

サンプル コードが実際のコードに可能な限り近く、 で遅延が観察されない場合boost::asio::async_readReadHandlerは実際のコードで実行する準備ができている可能性がありますが、同期 (ストランド) またはリソースを待機しています。 (スレッド)、遅延が発生します:

  • 遅延がストランドとの同期の結果である場合は、メッセージごとに必要な読み取り量を潜在的に削減するために、より大きなデータ ブロックを読み取ることによるRobinの提案を検討してください。
  • 遅延がスレッドの待機の結果である場合は、スレッド呼び出しを追加することを検討してio_service::run()ください。
于 2012-09-11T16:18:24.460 に答える
5

Boost.Asioを素晴らしいものにしていることの1つは、非同期機能を最大限に活用することです。1つのバッチで読み取られた特定のバイト数に依存すること、おそらくすでに読み取られたものの一部を捨てることは、実際にはあなたがすべきことではありません。

代わりに、特に次のWebサーバーの例を見てください。http ://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/example/http/server/connection.cpp

ブーストトリブールは、a)すべてのデータが1つのバッチで利用できる場合はリクエストを完了する、b)利用できるが有効ではない場合はリクエストを破棄する、c)io_serviceがリクエストが不完全であるかどうかを選択したときに詳細を読み取るために使用されます。接続オブジェクトは、共有ポインターを介してハンドラーと共有されます。

なぜこれが他のほとんどの方法よりも優れているのですか?すでにリクエストを解析している読み取り間の時間を節約できる可能性があります。残念ながら、この例ではこれを実行していませんが、理想的にはハンドラーをスレッド化して、残りがバッファーに追加されている間、既に使用可能なデータを処理できるようにします。それがブロックしているのは、データが不完全なときだけです。

これがお役に立てば幸いですが、読み取りの間に3ミリ秒の遅延がある理由を明らかにすることはできません。

于 2012-09-02T18:12:00.157 に答える