[英]how to create boost::async_read and async_write with timeout
我正在嘗試使用boost::asio
編寫服務器,但我希望boost::asio::async_read
操作在沒有數據到來的情況下超時,但我可以弄清楚該怎么做。 這是我到目前為止的代碼
void do_read_header() {
auto self(shared_from_this());
std::cout << "do_read_header\n";
boost::asio::async_read(
socket_, boost::asio::buffer(res.data(), res.header_length),
[this, self](boost::system::error_code ec,
std::size_t length) {
if (!ec && res.decode_header()) {
do_read_body();
}
});
do_write();
}
void do_read_body() {
auto self(shared_from_this());
Message msg;
std::cout << "do_read_body\n";
boost::asio::async_read(
socket_, boost::asio::buffer(res.body(), res.body_length()),
[this, self](boost::system::error_code ec,
std::size_t length) {
if (!length) {
return;
}
if (!ec) {
try {
std::cout << "read " << res.body() << "\n";
request_queue_.send(res.body(), res.body_length(),
0);
} catch (const std::exception& ex) {
std::cout << ex.what() << "\n";
}
} else {
if (ec) {
std::cerr << "read error:" << ec.value()
<< " message: " << ec.message() << "\n";
}
socket_.close();
}
do_read_header();
});
}
void start() {
post(strand_, [this, self = shared_from_this()] {
do_read_header();
do_write();
});
}
class Server {
public:
Server(boost::asio::io_service& io_service, short port)
: acceptor_(io_service, tcp::endpoint(tcp::v4(), port)),
socket_(io_service) {
do_accept();
}
private:
void do_accept() {
acceptor_.async_accept(
socket_, [this](boost::system::error_code ec) {
if (!ec) {
std::cout << "accept connection\n";
std::make_shared<Session>(std::move(socket_))
->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
tcp::socket socket_;
};
您可以添加取消 IO 操作的截止時間計時器。 您可以觀察到取消,因為將使用error::operation_aborted
調用完成。
deadline_.expires_from_now(1s);
deadline_.async_wait([self, this] (error_code ec) {
if (!ec) socket_.cancel();
});
我花了大約 45 分鍾使您的代碼獨立的 rest:
另請注意,我們避免關閉套接字 - 這是在會話的析構函數中完成的。 最好優雅地關閉。
#include <boost/asio.hpp>
#include <boost/endian/arithmetic.hpp>
#include <boost/interprocess/ipc/message_queue.hpp>
#include <iomanip>
#include <iostream>
using namespace std::chrono_literals;
namespace bip = boost::interprocess;
using boost::asio::ip::tcp;
using boost::system::error_code;
using Queue = boost::interprocess::message_queue;
static constexpr auto MAX_MESG_LEN = 100;
static constexpr auto MAX_MESGS = 10;
struct Message {
using Len = boost::endian::big_uint32_t;
struct header_t {
Len len;
};
static const auto header_length = sizeof(header_t);
std::array<char, MAX_MESG_LEN + header_length> buf;
char const* data() const { return buf.data(); }
char* data() { return buf.data(); }
char const* body() const { return data() + header_length; }
char* body() { return data() + header_length; }
static_assert(std::is_standard_layout_v<header_t> and
std::is_trivial_v<header_t>);
Len body_length() const { return std::min(h().len, max_body_length()); }
Len max_body_length() const { return buf.max_size() - header_length; }
bool decode_header() { return h().len <= max_body_length(); }
bool set_body(std::string_view value) {
assert(value.length() <= max_body_length());
h().len = value.length();
std::copy_n(value.begin(), body_length(), body());
return (value.length() == body_length()); // not truncated
}
private:
header_t& h() { return *reinterpret_cast<header_t*>(data()); }
header_t const& h() const { return *reinterpret_cast<header_t const*>(data()); }
};
struct Session : std::enable_shared_from_this<Session> {
Session(tcp::socket&& s) : socket_(std::move(s)) {}
void start() {
post(strand_,
[ this, self = shared_from_this() ] { do_read_header(); });
}
private:
using Strand = boost::asio::strand<tcp::socket::executor_type>;
using Timer = boost::asio::steady_timer;
tcp::socket socket_{strand_};
Strand strand_{make_strand(socket_.get_executor())};
Message res;
Queue request_queue_{bip::open_or_create, "SendQueue", MAX_MESGS, MAX_MESG_LEN};
Timer recv_deadline_{strand_};
void do_read_header() {
auto self(shared_from_this());
std::cout << "do_read_header: " << res.header_length << std::endl;
recv_deadline_.expires_from_now(5s);
recv_deadline_.async_wait([ self, this ](error_code ec) {
if (!ec) {
std::cerr << "header timeout" << std::endl;
socket_.cancel();
}
});
boost::asio::async_read(
socket_, boost::asio::buffer(res.data(), res.header_length),
[ this, self ](error_code ec, size_t /*length*/) {
std::cerr << "header: " << ec.message() << std::endl;
recv_deadline_.cancel();
if (!ec && res.decode_header()) {
do_read_body();
} else {
socket_.shutdown(tcp::socket::shutdown_both);
}
});
}
void do_read_body() {
auto self(shared_from_this());
// Message msg;
std::cout << "do_read_body: " << res.body_length() << std::endl;
recv_deadline_.expires_from_now(1s);
recv_deadline_.async_wait([self, this] (error_code ec) {
if (!ec) {
std::cerr << "body timeout" << std::endl;
socket_.cancel();
}
});
boost::asio::async_read(
socket_,
boost::asio::buffer(res.body(), res.body_length()),
boost::asio::transfer_exactly(res.body_length()),
[ this, self ](error_code ec, std::size_t length) {
std::cerr << "body: " << ec.message() << std::endl;
recv_deadline_.cancel();
if (!ec) {
try {
// Not safe to print unless NUL-terminated, see e.g.
// https://stackoverflow.com/questions/66278813/boost-deadline-timer-causes-stack-buffer-overflow/66279497#66279497
if (length)
request_queue_.send(res.body(), res.body_length(), 0);
} catch (const std::exception& ex) {
std::cout << ex.what() << std::endl;
}
do_read_header();
} else {
socket_.shutdown(tcp::socket::shutdown_both);
}
});
}
};
class Server {
public:
Server(boost::asio::io_service& io_service, short port)
: acceptor_(io_service, tcp::endpoint(tcp::v4(), port)),
socket_(io_service) {
do_accept();
}
private:
void do_accept() {
acceptor_.async_accept(socket_, [ this ](error_code ec) {
std::cerr << "async_accept: " << ec.message() << std::endl;
if (!ec) {
std::cerr << "session: " << socket_.remote_endpoint() << std::endl;
std::make_shared<Session>(std::move(socket_))->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
tcp::socket socket_;
};
int main(int argc, char**) {
Queue queue{bip::open_or_create, "SendQueue", MAX_MESGS, MAX_MESG_LEN}; // ensure it exists
if (argc == 1) {
boost::asio::io_context ioc;
Server s(ioc, 8989);
ioc.run_for(10s);
} else {
while (true) {
using Buf = std::array<char, MAX_MESG_LEN>;
Buf buf;
unsigned prio;
size_t n;
queue.receive(buf.data(), buf.size(), n, prio);
std::cout << "Received: " << std::quoted(std::string_view(buf.data(), n)) << std::endl;
}
}
}
可測試
./sotest
在另一個終端:
./sotest consumer
在其他地方,例如一些不會超時的請求:
for msg in '0000 0000' '0000 0001 31' '0000 000c 6865 6c6c 6f20 776f 726c 640a'
do
xxd -r -p <<< "$msg" |
netcat localhost 8989 -w 1
done
或者,對單個 session 的多請求,然后 session 超時( -w 6
超過 5s):
msg='0000 0000 0000 0001 31 0000 000c 6865 6c6c 6f20 776f 726c 640a'; xxd -r -p <<< "$msg"| netcat localhost 8989 -w 6
在實現串行端口包裝器時,我必須解決類似的問題,這是代碼的簡化版本:
// Possible outcome of a read. Set by callback
enum class ReadResult
{
ResultInProgress,
ResultSuccess,
ResultError,
ResultTimeout
};
std::streamsize read(char* s, std::streamsize n, boost::posix_time::time_duration timeout)
{
boost::asio::io_service io;
boost::asio::serial_port port(io);
// result is atomic to avoid race condition
std::atomic<ReadResult> result(ReadResult::ResultInProgress);
std::streamsize bytesTransferred = 0;
// Create async timer that fires after duration
boost::asio::deadline_timer timer(io);
timer.expires_from_now(timeout);
timer.async_wait(boost::bind([&result](const boost::system::error_code& error){
// If there wasn't any error and reading is still in progress, set result as timeout
if (!error && result == ReadResult::ResultInProgress)
result = ReadResult::ResultTimeout;
},boost::asio::placeholders::error));
// Read asynchronously
port.async_read_some(boost::asio::buffer(s, n), boost::bind([&result,&bytesTransferred](const boost::system::error_code& error,
const size_t transferred){
// If there wasn't any error on read finish set result as sucess else as error
if (!error){
result = ReadResult::ResultSuccess;
bytesTransferred = transferred;
return;
}
result = ReadResult::ResultError;
},boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
// Run loop until timeout, error or success occurs
for (;;)
{
io.run_one();
switch (result)
{
case ReadResult::ResultSuccess:
// Success, cancel timer and return amount of bytes read
timer.cancel();
return bytesTransferred;
case ReadResult::ResultTimeout:
// Timeout occured, cancel read and throw exception
port.cancel();
throw(TimeoutException("Timeout expired"));
case ReadResult::ResultError:
// Error occured, cancel read and timer and throw exception
port.cancel();
timer.cancel();
throw(std::ios_base::failure("Error while reading"));
default:
//if result is still in progress remain in the loop
break;
}
}
}
所以基本上,你要做的是:
io.run_one()
很重要您可以將它用於任何異步 function
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.