从boost::asio套接字异步读取,超时



我有以下代码:

class Connection
{
public:
Connection(boost::asio::io_context& ctx) 
: context(ctx),
timer(ctx),
socket(ctx)
{
buf.resize(10000);
}
void connect(const std::string& ip, uint16_t port)
{
boost::system::error_code code;
boost::asio::ip::tcp::endpoint ep(ip::address::from_string(ip), port);
socket.connect(ep, code);
std::cout << "Connection status " << code.message() << std::endl;
}
void write()
{
std::string request;
request += "GET / HTTP/1.1rn";
request += "Host: hostname.comrnrn";
std::cout << "Trying to write..." << std::endl;
boost::asio::async_write(socket, boost::asio::buffer(request), transfer_all(),
[=](const boost::system::error_code& e, std::size_t len)
{
std::cout << "request size is " << request.size() << std::endl;
std::cout << "written size is " << len << std::endl;
});
}
void read()
{
timer.expires_from_now(boost::posix_time::milliseconds(4000));
timer.async_wait([this](const boost::system::error_code& e)
{
if (!e) {
std::cout << "Socket closed by timer" << std::endl;
socket.close();
}
});
async_read(socket, boost::asio::buffer(buf), transfer_at_least(1), 
[this](const boost::system::error_code& e, std::size_t len)
{
std::cout << "read callback" << std::endl;
sleep(1);
std::cout << buf.substr(0, len);
if (!e)
read();
});
}
private:
std::string buf;
boost::asio::io_context& context;
boost::asio::deadline_timer timer;
boost::asio::ip::tcp::socket socket;
};
int main(int argc, char** argv)
{
boost::asio::io_context ctx;
Connection connection(ctx);

/*std::thread runThread([&ctx]()
{

});*/
//sleep(2);
connection.connect("ip_address", 80);
connection.write();
boost::shared_ptr<io_context::work> work(new io_context::work(ctx));
std::cout << "Waiting for read..." << std::endl;
connection.read();
ctx.run();
}

我希望它能这样工作:当调用read函数时,我们每次都会更新计时器。如果计时器在数据可供读取之前过期,我们将关闭套接字,从而取消异步读取操作。我在网上读到计时器可能有虚假的唤醒,但我没有完全弄清楚。这个代码的可靠性如何?

套接字和计时器使用相同的io_context

您的write函数存在大问题。您正在临时(本地(request上启动async_write(您甚至可以从完成处理程序访问request(。这导致了未定义的行为。

您的时间回调看起来不错,只是它可能更具体地描述错误代码。最有可能的是";虚假唤醒";将指当定时器被取消时用CCD_ 4完成。当您重置它(使用expires_from_now(时,也会发生这种情况。

我可能会写得更像

timer.expires_from_now(4000ms);
timer.async_wait([this](error_code ec) {
if (ec != boost::asio::error::operation_aborted)
return;
std::cout << "Socket closed by timer" << std::endl;
socket.cancel();
});

注意cancel(),它应该足够了。

以下是使用一些简化和良好实践的重做:

在Coliru上直播

//#define BOOST_ASIO_ENABLE_HANDLER_TRACKING 1
#include <boost/asio.hpp>
#include <iostream>
using boost::asio::ip::tcp;
using boost::system::error_code;
using std::this_thread::sleep_for;
using namespace std::chrono_literals;
class Connection {
public:
template <typename Executor>
Connection(Executor ex) : timer(ex)
, socket(ex) {
buf.resize(10000);
}
void connect(const std::string& ip, uint16_t port) {
std::cout << "Connecting" << std::endl;
auto addr = boost::asio::ip::address::from_string(ip);
socket.connect({addr, port});
std::cout << "Connected to " << socket.remote_endpoint() << std::endl;
}
void write() {
std::cout << "Trying to write..." << std::endl;
async_write( //
socket, boost::asio::buffer(request),
// boost::asio::transfer_all(),
[this](error_code ec, size_t transferred) {
std::cout << "request size is " << request.size() << "n"
<< "written size is " << transferred << " ("
<< ec.message() << ")" << std::endl;
});
}
void read() {
timer.expires_from_now(4000ms);
timer.async_wait([this](error_code ec) {
if (ec != boost::asio::error::operation_aborted)
return;
socket.cancel();
});
async_read( //
socket, boost::asio::buffer(buf), boost::asio::transfer_at_least(1),
[this](error_code ec, size_t transferred) {
if (ec == boost::asio::error::operation_aborted) {
std::cout << "Socket closed by timer" << std::endl;
return;
}
std::cout << "read callback (" << ec.message() << ", "
<< transferred << " bytes)" << std::endl;
sleep_for(1s);
//std::cout << buf.substr(0, transferred);
if (!ec)
read();
});
}
private:
std::string request = "GET / HTTP/1.1rnHost: www.example.comrnrn";
std::string buf;
boost::asio::steady_timer timer;
tcp::socket               socket;
};
int main() {
boost::asio::io_context ctx;
// strand not required unless multi-threading:
Connection connection(make_strand(ctx.get_executor()));
sleep_for(2s);
connection.connect("93.184.216.34", 80);
connection.write();
connection.read();
//auto work = make_work_guard(ctx); // not required
std::cout << "Waiting for read..." << std::endl;
ctx.run();
}

打印

Connecting
Connected to 93.184.216.34:80
Trying to write...
Waiting for read...
request size is 41
written size is 41 (Success)
read callback (Success, 1591 bytes)
Socket closed by timer

现在,我不会打扰transfer_at_least和朋友了。如果需要,可以将async_read_until与类似"rnrn"的完成条件或边界一起使用。我可能也只是接收到一个动态缓冲区:

async_read_until( //
socket, boost::asio::dynamic_buffer(buf), "rnrn",
[this](error_code ec, size_t transferred) {
if (ec == boost::asio::error::operation_aborted) {
std::cout << "Socket closed by timer" << std::endl;
return;
}
std::cout << "read callback (" << ec.message() << ", "
<< transferred << " bytes)n"
<< "Headers only: "
<< std::string_view(buf).substr(0, transferred)
<< "Remaining in buffer (start of body): "
<< buf.size() - transferred << std::endl;
});

现在您可以看到:实时

Connecting
Connected to 93.184.216.34:80
Trying to write...
Waiting for read...
request size is 41
written size is 41 (Success)
read callback (Success, 335 bytes)
Headers only: HTTP/1.1 200 OK
Age: 550281
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Sat, 13 Aug 2022 11:52:28 GMT
Etag: "3147526947+ident"
Expires: Sat, 20 Aug 2022 11:52:28 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECS (chb/0286)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 1256
Remaining in buffer (start of body): 177

额外奖励:使用图书馆

事实上,我会使用Boost Beast:在Coliru上直播

#include <boost/beast.hpp>
#include <iostream>
using boost::asio::ip::tcp;
using namespace std::chrono_literals;
namespace beast = boost::beast;
namespace http  = beast::http;
int main() {
boost::asio::io_context ctx;
beast::tcp_stream       conn(ctx.get_executor());
conn.connect({boost::asio::ip::address::from_string("93.184.216.34"), 80});
conn.expires_after(1000ms);
auto req = http::request<http::empty_body>(http::verb::get, "/", 11);
req.set(http::field::host, "www.example.com");
write(conn, req);
conn.expires_after(4000ms);
http::response<http::string_body> res;
beast::flat_buffer buf;
read(conn, buf, res);
std::cout << "Headers: "       << res.base()          << "n";
std::cout << "Body received: " << res.body().length() << "n";
}

打印

Headers: HTTP/1.1 200 OK
Age: 551081
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Sat, 13 Aug 2022 12:05:48 GMT
Etag: "3147526947+ident"
Expires: Sat, 20 Aug 2022 12:05:48 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECS (chb/0286)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 1256
Body received: 1256

最新更新