C++ asio异步服务器设计
目前我使用的设计是,服务器读取流的前4个字节,然后在头解码后读取N个字节 但我发现第一次异步读取和第二次读取之间的时间是3-4毫秒。我只是在控制台中打印了回调的时间戳以进行测量。我总共发送了10字节的数据。为什么阅读要花这么多时间 我在调试模式下运行它,但我认为调试的1个连接是 从套接字读取数据之间的延迟不超过3毫秒。也许我需要 另一种在“数据包”上切断TCP流的方法 更新:我在这里发布了一些代码C++ asio异步服务器设计,c++,networking,boost,architecture,boost-asio,C++,Networking,Boost,Architecture,Boost Asio,目前我使用的设计是,服务器读取流的前4个字节,然后在头解码后读取N个字节 但我发现第一次异步读取和第二次读取之间的时间是3-4毫秒。我只是在控制台中打印了回调的时间戳以进行测量。我总共发送了10字节的数据。为什么阅读要花这么多时间 我在调试模式下运行它,但我认为调试的1个连接是 从套接字读取数据之间的延迟不超过3毫秒。也许我需要 另一种在“数据包”上切断TCP流的方法 更新:我在这里发布了一些代码 void parseHeader(const boost::system::error_code&
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)));
}
void parseHeader(const boost::system::error\u代码和错误)
{
cout有一件事让Boost变得强大。Asio Aweasome充分利用了异步功能。依赖于一批中读取的特定字节数,可能会丢弃一些已经读取的字节,这并不是你真正应该做的
相反,请看Web服务器的示例,尤其是以下示例:
boost Tribolean用于A)如果一批中的所有数据都可用,则完成请求;b)如果可用但无效,则丢弃请求;c)如果请求不完整,则io_服务选择读取更多数据。连接对象通过共享指针与处理程序共享
为什么这比大多数其他方法都好?您可能可以节省已经解析请求的读取之间的时间。遗憾的是,本示例中没有遵循这一点,但理想的情况是您将处理程序线程化,以便它可以处理已经可用的数据,而其余的则添加到缓冲区中。它阻塞的唯一时间是数据不完整时
希望这有帮助,但不能解释为什么读取之间会有3ms的延迟。有太多的未知因素,无法从发布的代码中确定延迟的根本原因。不过,可以采取一些方法和注意事项来帮助确定问题:
- 启用Boost.Asio 1.47+。只需定义
Boost\u Asio\u Enable\u HANDLER\u TRACKING
和Boost即可。Asio将调试输出(包括时间戳)写入标准错误流。这些时间戳可用于帮助过滤应用程序代码引入的延迟(parseHeader()
,parsePacket()
,等等)
- 验证是否正确处理。例如,如果协议将标头的
大小
字段定义为网络字节顺序的两个字节,并且服务器将该字段作为原始短消息处理,则在收到正文大小为10
的消息时:
- big-endian机器将调用
async\u read
读取10
字节。读取操作应该很快完成,因为套接字已具有可读取的10
字节体
- 一台小型endian机器将调用
async_read
reading2560
字节。由于尝试读取的字节数远远超过预期,因此读取操作可能会保持未完成状态
- 使用跟踪工具,如,等
- 修改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;
}
注意到没有延迟,我对示例进行了扩展,修改了boost::asio::async\u read
调用,将this
替换为shared\u from\u this()
,并将ReadHandlers
s包装为strand\u.wrap()
。我运行了更新后的示例,但仍然没有发现延迟。不幸的是,根据问题中发布的代码,我只能做到这一点
考虑扩展示例,在每次迭代中从实际实现中添加一部分。例如:
- 首先使用
msg
变量的类型来控制缓冲区
- 接下来,发送有效数据,并引入
parseHeader()
和parsePacket
函数
- 最后,介绍
lib::GET\u SERVER\u TIME()
print
如果示例代码尽可能接近真实代码,并且使用boost::asio::async_read
未观察到延迟,则ReadHandler
s可能准备在真实代码中运行,但它们正在等待同步(串)或资源(线程),从而导致延迟:
- 如果延迟是与链同步的结果,那么通过读取较大的数据块来考虑建议,以潜在地减少每个消息所需的读取量。
- 如果延迟是等待线程的结果,那么考虑有一个附加的线程调用<代码> IoService:::()/代码> ./LI>
这个问题不清楚。引用的是什么?是否来自上一个问题?您是否可以编辑您的问题,以包括一个问题:您如何确定服务器中存在3-4毫秒的延迟,而不是由于客户端造成的延迟?@DenisErmolin您不应该在调试模式下测量计时。性能影响可能只有10%或高达1%0k%+代码片段不是非常有用。我们无法编译它来重现问题,请发布完整的复制程序或告诉我们。如果您在Linux上,则输出“strace-f-tt-T”对于该应用程序可能值得发布。@DenisErmolin:我编译了两次每个示例:-g-O0
用于调试,而-O3-s-DNDEBUG
用于发布。重新研究后
$ ./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