Boost.Asio: 异步操作超时
Boost.Asio: Async operations timeout
我的程序充当客户端可以连接的服务器。客户端连接后,他将每 ~5 秒从服务器获取更新一次。这是每 5 秒调用一次以将新数据发送到客户端的 write
函数:
void NIUserSession::write(std::string &message_orig)
{
std::cout << "Writing message" << std::endl;
std::shared_ptr<std::string> message = std::make_shared<std::string>( message_orig );
message->append("<EOF>");
boost::system::error_code ec;
boost::asio::async_write(this->socket_, boost::asio::buffer(*message),
boost::asio::transfer_all(), boost::bind(&NIUserSession::writeHandler,
this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred(),
message
));
}
void NIUserSession::writeHandler(const boost::system::error_code &error, std::size_t bytes_transferred, std::shared_ptr<std::string> message)
{
std::cout << "Write Handler" << std::endl;
if(error)
{
std::cout << "Write handler error: " << error.message() << std::endl;
this->disconnect();
}
}
void NIUserSession::disconnect()
{
std::cout << "Disconnecting client, cancling all write and read operations." << std::endl;
this->socket_.lowest_layer().cancel();
delete this;
}
如果写入操作出错,服务器和客户端之间的连接将关闭,所有异步操作将被取消 (this->socket_.lowest_layer().cancel();
)。
问题是如果连接超时,writeHandler
不会立即被调用。相反,写入操作 "stack up" 直到第一个达到 writeHandler
.
这应该是程序的正常输出:
Writing message
Write Handler
... Other stuff ...
... Other stuff ...
Writing message
Write Handler
如果连接超时,会发生以下情况:
Writing message
Write Handler
Write handler error: Connection timed out
Disconnecting client, cancling all write and read operations.
Write Handler
Write Handler
Write Handler
Write Handler
Write Handler
Write Handler
Write Handler
Write Handler
Write Handler
Write Handler
Write Handler
Segmentation fault
最后,出现分段错误。我认为这是因为 disconnect
在其他异步操作仍在进行时被调用。
我以为我可以在第一次异步操作失败后直接使用 this->socket_.lowest_layer().cancel();
来避免它,但它不起作用。
如何避免分段错误?
嗯,取消操作时不应该删除 this
,因为挂起的 I/O 操作的回调仍将被调用,然后访问 this
会导致未定义的行为。有多种方法可以解决这个问题:
- 在您真正知道之前的数据已写入之前不要写入数据。您可以将传递给
NIUserSession::write
的 std::string
实例排队,以防未完成的写入仍未决,然后在未完成的写入操作完成时将它们实际写入处理程序。这样你就不会在飞行中有多个 I/O 操作。
- 继承自
std::enable_shared_from_this
并将 shared_from_this()
而不是 this
传递给 async_write
调用(这就是 Boost asynchronous TCP daytime server example 所做的)。这样,挂起的 I/O 操作将保留对您的 class 的引用,如果所有操作都完成,将调用析构函数。
我的程序充当客户端可以连接的服务器。客户端连接后,他将每 ~5 秒从服务器获取更新一次。这是每 5 秒调用一次以将新数据发送到客户端的 write
函数:
void NIUserSession::write(std::string &message_orig)
{
std::cout << "Writing message" << std::endl;
std::shared_ptr<std::string> message = std::make_shared<std::string>( message_orig );
message->append("<EOF>");
boost::system::error_code ec;
boost::asio::async_write(this->socket_, boost::asio::buffer(*message),
boost::asio::transfer_all(), boost::bind(&NIUserSession::writeHandler,
this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred(),
message
));
}
void NIUserSession::writeHandler(const boost::system::error_code &error, std::size_t bytes_transferred, std::shared_ptr<std::string> message)
{
std::cout << "Write Handler" << std::endl;
if(error)
{
std::cout << "Write handler error: " << error.message() << std::endl;
this->disconnect();
}
}
void NIUserSession::disconnect()
{
std::cout << "Disconnecting client, cancling all write and read operations." << std::endl;
this->socket_.lowest_layer().cancel();
delete this;
}
如果写入操作出错,服务器和客户端之间的连接将关闭,所有异步操作将被取消 (this->socket_.lowest_layer().cancel();
)。
问题是如果连接超时,writeHandler
不会立即被调用。相反,写入操作 "stack up" 直到第一个达到 writeHandler
.
这应该是程序的正常输出:
Writing message
Write Handler
... Other stuff ...
... Other stuff ...
Writing message
Write Handler
如果连接超时,会发生以下情况:
Writing message
Write Handler
Write handler error: Connection timed out
Disconnecting client, cancling all write and read operations.
Write Handler
Write Handler
Write Handler
Write Handler
Write Handler
Write Handler
Write Handler
Write Handler
Write Handler
Write Handler
Write Handler
Segmentation fault
最后,出现分段错误。我认为这是因为 disconnect
在其他异步操作仍在进行时被调用。
我以为我可以在第一次异步操作失败后直接使用 this->socket_.lowest_layer().cancel();
来避免它,但它不起作用。
如何避免分段错误?
嗯,取消操作时不应该删除 this
,因为挂起的 I/O 操作的回调仍将被调用,然后访问 this
会导致未定义的行为。有多种方法可以解决这个问题:
- 在您真正知道之前的数据已写入之前不要写入数据。您可以将传递给
NIUserSession::write
的std::string
实例排队,以防未完成的写入仍未决,然后在未完成的写入操作完成时将它们实际写入处理程序。这样你就不会在飞行中有多个 I/O 操作。 - 继承自
std::enable_shared_from_this
并将shared_from_this()
而不是this
传递给async_write
调用(这就是 Boost asynchronous TCP daytime server example 所做的)。这样,挂起的 I/O 操作将保留对您的 class 的引用,如果所有操作都完成,将调用析构函数。