[英]boost::asio async server design
目前,服务器正在读取流的前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个字节。 我该如何调试实际延迟时间?
太多的未知因素无法从发布的代码中识别出延迟的根本原因。 但是,可以采取一些方法和考虑因素来帮助确定问题:
BOOST_ASIO_ENABLE_HANDLER_TRACKING
和Boost.Asio将调试输出(包括时间戳)写入标准错误流。 这些时间戳可用于帮助滤除应用程序代码( parseHeader()
, parsePacket()
等)引入的延迟。 size
字段定义为按网络字节顺序的两个字节,并且服务器将其作为原始short来处理,则在接收到正文大小为10
的消息时:
async_read
读取10
个字节。 读取操作应迅速完成,因为套接字已具有10
字节的正文可读取。 async_read
机器将调用async_read
读取2560
字节。 读取操作可能会保持未完成状态,因为尝试读取的字节超出了预期的范围。 这是我开始的一个简单示例:
#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_read
,通过以下方法将噪声降至最低:
shared_from_this()
或strand::wrap
。 我使用gcc 4.4.0和Boost 1.50在CentOS 5.4上进行了编译。 为了驱动数据,我选择使用netcat发送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
观察无延迟,我通过修改后的例子扩展boost::asio::async_read
调用,免去this
与shared_from_this()
;和包装ReadHandlers
s的strand_.wrap()
我运行了更新的示例,但仍然没有发现延迟。 不幸的是,这是我根据问题中发布的代码所能得到的。
考虑扩展示例,在每次迭代中添加真实实现中的一部分。 例如:
msg
变量的类型来控制缓冲区。 parseHeader()
和parsePacket
函数。 lib::GET_SERVER_TIME()
打印。 如果示例代码与真实代码尽可能接近,并且boost::asio::async_read
没有观察到延迟,则ReadHandler
可能已准备好在真实代码中运行,但它们正在等待同步(链)或资源(线程),导致延迟:
io_service::run()
。 使Boost.Asio很棒的一件事是充分利用了异步功能。 依靠一批中读取的特定字节数,可能放弃一些已经可以读取的字节,并不是您真正应该做的。
相反,请查看网络服务器的示例,尤其是以下示例: http : //www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/example/http/server/connection.cpp
一个boost triboolean用于要么a)如果所有数据都可以在一批中完成,要么b)如果它可用但无效则放弃它; c)如果io_service选择请求不完整,则仅阅读更多内容。 连接对象通过共享指针与处理程序共享。
为什么这优于大多数其他方法? 您可以节省已经解析请求的两次读取之间的时间。 遗憾的是,在示例中没有遵循此规则,但是理想情况下,您可以对处理程序进行线程化,以便它可以处理已经可用的数据,而将其余数据添加到缓冲区中。 阻塞的唯一时间是数据不完整时。
希望这会有所帮助,但是不能阐明为什么读取之间会有3ms的延迟。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.