通過使用官方Boost timeout示例中使用的方法,解決構成問題的一種便捷方式是管理由多個非鏈式異步操作組成的更高級別的異步操作。在其中,處理程序通過檢查當前狀態來做出決定,而不是將處理程序邏輯與預期狀態或提供狀態耦合。
在開始解決方案之前,確定處理程序執行的所有可能情況非常重要。當運行io_service
時,事件循環的一次迭代將執行所有可以運行的操作,並且在完成操作後,用戶的完成處理程序將排隊,並指示操作的狀態爲error_code
。然後io_service
將調用排隊的完成處理程序。因此,在一次迭代中,所有準備運行的操作在完成處理程序之前以未指定的順序執行,並且未指定完成處理程序的調用順序。例如,組成從async_read()
和async_wait()
,其中任操作只其它操作的完成處理程序中取消了async_read_with_timeout()
操作時,下面的情況是可能的:
async_read()
運行和async_wait()
還沒有準備好運行,然後async_read()
的完成處理程序被調用並取消async_wait()
,導致async_wait()
的完成處理程序運行時出現錯誤boost::asio::error::operation_aborted
。
async_read()
還沒有準備好運行和async_wait()
運行,然後async_wait()
的完成處理程序被調用,並取消async_read()
,造成async_read()
的完成處理程序與的boost::asio::error::operation_aborted
錯誤運行。
async_read()
和async_wait()
運行,然後async_read()
的完成處理程序首先被調用,但async_wait()
操作已經完成,並且無法取消,所以async_wait()
的完成處理程序將沒有錯誤運行。
async_read()
和async_wait()
運行,然後async_wait()
的完成處理程序首先被調用,但async_read()
操作已經完成,並且無法取消,所以async_read()
的完成處理程序將沒有錯誤運行。
完成處理程序的error_code
指示操作的狀態,並不反映由其他完成處理程序導致的狀態更改;因此,當error_code
成功時,可能需要檢查當前狀態以執行條件分支。但是,在引入附加狀態之前,可能需要努力檢查更高級別操作的目標以及已經可用的狀態。在這個例子中,我們定義async_read_with_timeout()
的目標是在到達截止日期之前沒有收到數據的情況下關閉套接字。對於狀態,插座是打開或關閉的;定時器提供到期時間;系統時鐘提供當前時間。檢查的目標和可用的狀態信息之後,可以建議:如果計時器的當前到期時間是在過去
async_wait()
的處理程序應該只關閉套接字。
async_read()
的處理程序應該設置計時器的到期時間。
這種做法,如果async_read()
的完成處理async_wait()
之前運行,那麼無論async_wait()
將被取消或async_wait()
的完成處理程序不會關閉連接,作爲當前的到期時間是在未來。另一方面,如果async_wait()
的完成處理程序在async_read()
之前運行,則async_read()
將被取消或async_read()
的完成處理程序可以檢測到套接字已關閉。
這是一個完整的小例子demonstrating這種方法用於各種用例:
#include <cassert>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
class client
{
public:
// This demo is only using status for asserting code paths. It is not
// necessary nor should it be used for conditional branching.
enum status_type
{
unknown,
timeout,
read_success,
read_failure
};
public:
client(boost::asio::ip::tcp::socket& socket)
: strand_(socket.get_io_service()),
timer_(socket.get_io_service()),
socket_(socket),
status_(unknown)
{}
status_type status() const { return status_; }
void async_read_with_timeout(boost::posix_time::seconds seconds)
{
strand_.post(boost::bind(
&client::do_async_read_with_timeout, this, seconds));
}
private:
void do_async_read_with_timeout(boost::posix_time::seconds seconds)
{
// Start a timeout for the read.
timer_.expires_from_now(seconds);
timer_.async_wait(strand_.wrap(boost::bind(
&client::handle_wait, this,
boost::asio::placeholders::error)));
// Start the read operation.
boost::asio::async_read(socket_,
boost::asio::buffer(buffer_),
strand_.wrap(boost::bind(
&client::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
void handle_wait(const boost::system::error_code& error)
{
// On error, such as cancellation, return early.
if (error)
{
std::cout << "timeout cancelled" << std::endl;
return;
}
// The timer may have expired, but it is possible that handle_read()
// ran succesfully and updated the timer's expiration:
// - a new timeout has been started. For example, handle_read() ran and
// invoked do_async_read_with_timeout().
// - there are no pending timeout reads. For example, handle_read() ran
// but did not invoke do_async_read_with_timeout();
if (timer_.expires_at() > boost::asio::deadline_timer::traits_type::now())
{
std::cout << "timeout occured, but handle_read ran first" << std::endl;
return;
}
// Otherwise, a timeout has occured and handle_read() has not executed, so
// close the socket, cancelling the read operation.
std::cout << "timeout occured" << std::endl;
status_ = client::timeout;
boost::system::error_code ignored_ec;
socket_.close(ignored_ec);
}
void handle_read(
const boost::system::error_code& error,
std::size_t bytes_transferred)
{
// Update timeout state to indicate handle_read() has ran. This
// cancels any pending timeouts.
timer_.expires_at(boost::posix_time::pos_infin);
// On error, return early.
if (error)
{
std::cout << "read failed: " << error.message() << std::endl;
// Only set status if it is unknown.
if (client::unknown == status_) status_ = client::read_failure;
return;
}
// The read was succesful, but if a timeout occured and handle_wait()
// ran first, then the socket is closed, so return early.
if (!socket_.is_open())
{
std::cout << "read was succesful but timeout occured" << std::endl;
return;
}
std::cout << "read was succesful" << std::endl;
status_ = client::read_success;
}
private:
boost::asio::io_service::strand strand_;
boost::asio::deadline_timer timer_;
boost::asio::ip::tcp::socket& socket_;
char buffer_[1];
status_type status_;
};
// This example is not interested in the connect handlers, so provide a noop
// function that will be passed to bind to meet the handler concept
// requirements.
void noop() {}
/// @brief Create a connection between the server and client socket.
void connect_sockets(
boost::asio::ip::tcp::acceptor& acceptor,
boost::asio::ip::tcp::socket& server_socket,
boost::asio::ip::tcp::socket& client_socket)
{
boost::asio::io_service& io_service = acceptor.get_io_service();
acceptor.async_accept(server_socket, boost::bind(&noop));
client_socket.async_connect(acceptor.local_endpoint(), boost::bind(&noop));
io_service.reset();
io_service.run();
io_service.reset();
}
int main()
{
using boost::asio::ip::tcp;
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
// Scenario 1: timeout
// The server writes no data, causing a client timeout to occur.
{
std::cout << "[Scenario 1: timeout]" << std::endl;
// Create and connect I/O objects.
tcp::socket server_socket(io_service);
tcp::socket client_socket(io_service);
connect_sockets(acceptor, server_socket, client_socket);
// Start read with timeout on client.
client client(client_socket);
client.async_read_with_timeout(boost::posix_time::seconds(0));
// Allow do_read_with_timeout to intiate actual operations.
io_service.run_one();
// Run timeout and read operations.
io_service.run();
assert(client.status() == client::timeout);
}
// Scenario 2: no timeout, succesful read
// The server writes data and the io_service is ran before the timer
// expires. In this case, the async_read operation will complete and
// cancel the async_wait.
{
std::cout << "[Scenario 2: no timeout, succesful read]" << std::endl;
// Create and connect I/O objects.
tcp::socket server_socket(io_service);
tcp::socket client_socket(io_service);
connect_sockets(acceptor, server_socket, client_socket);
// Start read with timeout on client.
client client(client_socket);
client.async_read_with_timeout(boost::posix_time::seconds(10));
// Allow do_read_with_timeout to intiate actual operations.
io_service.run_one();
// Write to client.
boost::asio::write(server_socket, boost::asio::buffer("test"));
// Run timeout and read operations.
io_service.run();
assert(client.status() == client::read_success);
}
// Scenario 3: no timeout, failed read
// The server closes the connection before the timeout, causing the
// async_read operation to fail and cancel the async_wait operation.
{
std::cout << "[Scenario 3: no timeout, failed read]" << std::endl;
// Create and connect I/O objects.
tcp::socket server_socket(io_service);
tcp::socket client_socket(io_service);
connect_sockets(acceptor, server_socket, client_socket);
// Start read with timeout on client.
client client(client_socket);
client.async_read_with_timeout(boost::posix_time::seconds(10));
// Allow do_read_with_timeout to intiate actual operations.
io_service.run_one();
// Close the socket.
server_socket.close();
// Run timeout and read operations.
io_service.run();
assert(client.status() == client::read_failure);
}
// Scenario 4: timeout and read success
// The server writes data, but the io_service is not ran until the
// timer has had time to expire. In this case, both the await_wait and
// asnyc_read operations complete, but the order in which the
// handlers run is indeterminiate.
{
std::cout << "[Scenario 4: timeout and read success]" << std::endl;
// Create and connect I/O objects.
tcp::socket server_socket(io_service);
tcp::socket client_socket(io_service);
connect_sockets(acceptor, server_socket, client_socket);
// Start read with timeout on client.
client client(client_socket);
client.async_read_with_timeout(boost::posix_time::seconds(0));
// Allow do_read_with_timeout to intiate actual operations.
io_service.run_one();
// Allow the timeout to expire, the write to the client, causing both
// operations to complete with success.
boost::this_thread::sleep_for(boost::chrono::seconds(1));
boost::asio::write(server_socket, boost::asio::buffer("test"));
// Run timeout and read operations.
io_service.run();
assert( (client.status() == client::timeout)
|| (client.status() == client::read_success));
}
}
,其輸出:
[Scenario 1: timeout]
timeout occured
read failed: Operation canceled
[Scenario 2: no timeout, succesful read]
read was succesful
timeout cancelled
[Scenario 3: no timeout, failed read]
read failed: End of file
timeout cancelled
[Scenario 4: timeout and read success]
read was succesful
timeout occured, but handle_read ran first
是的,我注意到了。實際上,async_wait()被調用很多次,所以我需要某種狀態變量每次都會增加嗎?額外綁定變量是通過引用綁定還是綁定值,還是取決於您如何定義綁定? – 2014-10-07 11:33:43
@SimonElliott這取決於你。你甚至可以選擇通過'boost :: ref(timer1)'傳遞一個對服務對象的引用(比如'deadline_timer')。注意:「async_wait()」被多次調用?但是,您不能在同一對象(套接字,計時器)上執行多個異步操作。您需要取消掛起的操作('timer1.expires_from_now()')或對操作進行排序(使用'strand'或者可能是協程) – sehe 2014-10-07 11:36:51