投稿されたコードから遅延の根本原因を特定するには、不明な点が多すぎます。それにもかかわらず、問題を特定するのに役立ついくつかのアプローチと考慮事項があります。
- Boost.Asio 1.47+ のハンドラー追跡を有効にします。定義するだけ
BOOST_ASIO_ENABLE_HANDLER_TRACKING
で、Boost.Asio はタイムスタンプを含むデバッグ出力を標準エラー ストリームに書き込みます。parseHeader()
これらのタイムスタンプは、アプリケーション コード ( 、parsePacket()
など)によって生じる遅延を除外するために使用できます。
- バイト順が適切に処理されていることを確認します。たとえば、プロトコルがヘッダーの
size
フィールドをネットワーク バイト順で 2 バイトとして定義し、サーバーがフィールドを raw short として処理している場合、ボディ サイズが のメッセージを受信すると、次のようになります10
。
- ビッグ エンディアン マシンは、
async_read
読み取り10
バイトを呼び出します。10
ソケットには読み取り可能なバイト本体がすでにあるため、読み取り操作はすぐに完了するはずです。
- リトルエンディアン マシンは、
async_read
reading 2560
bytes を呼び出します。意図したよりもはるかに多くのバイトが読み取られようとしているため、読み取り操作は未解決のままになる可能性があります。
- strace、ltraceなどのトレース ツールを使用します。
- 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
呼び出しを変更し、 を に置き換えthis
、shared_from_this()
でラップしReadHandlers
て、例を拡張しましたstrand_.wrap()
。更新された例を実行しましたが、それでも遅延は見られませんでした。残念ながら、それは質問に投稿されたコードに基づいて取得できる限りです。
反復ごとに実際の実装の一部を追加して、例を拡張することを検討してください。例えば:
msg
変数の型を使用してバッファを制御することから始めます。
- 次に、有効なデータを送信し、関数を導入
parseHeader()
しparsePacket
ます。
- 最後に
lib::GET_SERVER_TIME()
プリントを紹介します。
サンプル コードが実際のコードに可能な限り近く、 で遅延が観察されない場合boost::asio::async_read
、ReadHandler
は実際のコードで実行する準備ができている可能性がありますが、同期 (ストランド) またはリソースを待機しています。 (スレッド)、遅延が発生します:
- 遅延がストランドとの同期の結果である場合は、メッセージごとに必要な読み取り量を潜在的に削減するために、より大きなデータ ブロックを読み取ることによるRobinの提案を検討してください。
- 遅延がスレッドの待機の結果である場合は、スレッド呼び出しを追加することを検討して
io_service::run()
ください。