#include <boost/asio/buffer.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/read_until.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/write.hpp>
#include <functional>
#include <iostream>
#include <string>
using boost::asio::steady_timer;
using boost::asio::ip::tcp;
using std::placeholders::_1;
using std::placeholders::_2;
class client
{
public:
client(boost::asio::io_context& io_context)
: socket_(io_context),
deadline_(io_context),
heartbeat_timer_(io_context)
{
}
void start(tcp::resolver::results_type endpoints)
{
endpoints_ = endpoints;
start_connect(endpoints_.begin());
deadline_.async_wait(std::bind(&client::check_deadline, this));
}
void stop()
{
stopped_ = true;
boost::system::error_code ignored_error;
socket_.close(ignored_error);
deadline_.cancel();
heartbeat_timer_.cancel();
}
private:
void start_connect(tcp::resolver::results_type::iterator endpoint_iter)
{
if (endpoint_iter != endpoints_.end())
{
std::cout << "Trying " << endpoint_iter->endpoint() << "...\n";
deadline_.expires_after(std::chrono::seconds(60));
socket_.async_connect(endpoint_iter->endpoint(),
std::bind(&client::handle_connect,
this, _1, endpoint_iter));
}
else
{
stop();
}
}
void handle_connect(const boost::system::error_code& error,
tcp::resolver::results_type::iterator endpoint_iter)
{
if (stopped_)
return;
if (!socket_.is_open())
{
std::cout << "Connect timed out\n";
start_connect(++endpoint_iter);
}
else if (error)
{
std::cout << "Connect error: " << error.message() << "\n";
socket_.close();
start_connect(++endpoint_iter);
}
else
{
std::cout << "Connected to " << endpoint_iter->endpoint() << "\n";
start_read();
start_write();
}
}
void start_read()
{
deadline_.expires_after(std::chrono::seconds(30));
boost::asio::async_read_until(socket_,
boost::asio::dynamic_buffer(input_buffer_), '\n',
std::bind(&client::handle_read, this, _1, _2));
}
void handle_read(const boost::system::error_code& error, std::size_t n)
{
if (stopped_)
return;
if (!error)
{
std::string line(input_buffer_.substr(0, n - 1));
input_buffer_.erase(0, n);
if (!line.empty())
{
std::cout << "Received: " << line << "\n";
}
start_read();
}
else
{
std::cout << "Error on receive: " << error.message() << "\n";
stop();
}
}
void start_write()
{
if (stopped_)
return;
boost::asio::async_write(socket_, boost::asio::buffer("\n", 1),
std::bind(&client::handle_write, this, _1));
}
void handle_write(const boost::system::error_code& error)
{
if (stopped_)
return;
if (!error)
{
heartbeat_timer_.expires_after(std::chrono::seconds(10));
heartbeat_timer_.async_wait(std::bind(&client::start_write, this));
}
else
{
std::cout << "Error on heartbeat: " << error.message() << "\n";
stop();
}
}
void check_deadline()
{
if (stopped_)
return;
if (deadline_.expiry() <= steady_timer::clock_type::now())
{
socket_.close();
deadline_.expires_at(steady_timer::time_point::max());
}
deadline_.async_wait(std::bind(&client::check_deadline, this));
}
private:
bool stopped_ = false;
tcp::resolver::results_type endpoints_;
tcp::socket socket_;
std::string input_buffer_;
steady_timer deadline_;
steady_timer heartbeat_timer_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 3)
{
std::cerr << "Usage: client <host> <port>\n";
return 1;
}
boost::asio::io_context io_context;
tcp::resolver r(io_context);
client c(io_context);
c.start(r.resolve(argv[1], argv[2]));
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}