Elegant way of reconnecting loop with boost::asio? - c++

I am trying to write a very elegant way of handling a reconnect loop with boost async_connect(...). The problem is, I don't see a way how I could elegantly solve the following problem:
I have a TCP client that should try to connect asynchronously to a server, if the connection fails because the server is offline or any other error occurs, wait a given amount of time and try to reconnect. There are multiple things to take into consideration here:
Avoidance of global variables if possible
It has to be async connect
A very basic client is instantiated like so:
tcpclient::tcpclient(std::string host, int port) : _endpoint(boost::asio::ip::address::from_string(host), port), _socket(_ios) {
logger::log_info("Initiating client ...");
}
Attempt to connect to the server:
void tcpclient::start() {
bool is_connected = false;
while (!is_connected) {
_socket.async_connect(_endpoint, connect_handler);
_ios.run();
}
// read write data (?)
}
The handler:
void tcpclient::connect_handler(const boost::system::error_code &error) {
if(error){
// trigger disconnect (?)
logger::log_error(error.message());
return;
}
// Connection is established at this point
// Update timer state and start authentication on server ?
logger::log_info("Connected?");
}
How can I properly start reconnecting everytime the connection fails (or is dropped)? Since the handler is static I can not modify a class attribute that indicates the connection status? I want to avoid using hacky global variable workarounds.
How can I solve this issue in a proper way?
My attempt would be something like this:
tcpclient.h
enum ConnectionStatus{
NOT_CONNECTED,
CONNECTED
};
class tcpclient {
public:
tcpclient(std::string host, int port);
void start();
private:
ConnectionStatus _status = NOT_CONNECTED;
void connect_handler(const boost::system::error_code& error);
boost::asio::io_service _ios;
boost::asio::ip::tcp::endpoint _endpoint;
boost::asio::ip::tcp::socket _socket;
};
tcpclient.cpp
#include "tcpclient.h"
#include <boost/chrono.hpp>
#include "../utils/logger.h"
tcpclient::tcpclient(std::string host, int port) : _endpoint(boost::asio::ip::address::from_string(host), port),
_socket(_ios) {
logger::log_info("Initiating client ...");
logger::log_info("Server endpoint: " + _endpoint.address().to_string());
}
void tcpclient::connect_handler(const boost::system::error_code &error) {
if(!error){
_status = CONNECTED;
logger::log_info("Connected.");
}
else{
_status = NOT_CONNECTED;
logger::log_info("Failed to connect");
_socket.close();
}
}
void tcpclient::start() {
while (_status == NOT_CONNECTED) {
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
_socket.close();
_socket.async_connect(_endpoint, std::bind(&tcpclient::connect_handler, this, std::placeholders::_1));
_ios.run();
}
}
The problem is that the reconnect is not working properly and the application seems to freeze for some reason? Aside from that reconnecting also seems problematic once a connection was established and is then dropped (e.g. due to the server crashing/closing).

std::this_thread::sleep_for(std::chrono::milliseconds(2000)); will freeze program for 2 seconds. What can you do here is to launch async timer when connection attempt fails:
::boost::asio::steady_timer m_timer{_ios, boost::asio::chrono::seconds{2}};
void tcpclient::connect_handler(const boost::system::error_code &error)
{
if(!error)
{
_status = CONNECTED;
logger::log_info("Connected.");
}
else
{
_status = NOT_CONNECTED;
logger::log_info("Failed to connect");
_socket.close();
m_timer.expires_from_now(boost::asio::chrono::seconds{2});
m_timer.async_wait(std::bind(&tcpclient::on_ready_to_reconnect, this, std::placeholders::_1));
}
}
void tcpclient::on_ready_to_reconnect(const boost::system::error_code &error)
{
try_connect();
}
void tcpclient::try_connect()
{
m_socket.async_connect(_endpoint, std::bind(&tcpclient::connect_handler, this, std::placeholders::_1));
}
void tcpclient::start()
{
try_connect();
_ios.run();
}
There is also no need for while (_status == NOT_CONNECTED) loop, because io service will be busy and _ios.run(); won't return until connection is established.

Related

boost::asio::async_accept is in a infinite loop

I am creating a TCP server using boost::asio. I am listening 9012 port(I know this port is available) and opening a socket from a java program as such:
public static main(String args[]) {
Socket sock = new Socket("10.0.10.128",9012);
PrintWriter out = new PrintWriter(sock.getOutputStream(), true);
out.println(1);
while(true){}
}
On C++ side:
void TCPServer::do_accept() {
acceptor_obj.async_accept([this](const boost::system::error_code& ec, tcp::socket socket) {
if (!ec) {
std::make_shared<TCPSession>(std::move(socket))->start_async_read();
}
do_accept();
});
}
I expected it to only create a single TCPSession however it is in an infinite loop constantly creating TCPSession/s. Can anyone tell me why this might be the case?
edit: acceptor_obj was missing method call /facepalm

boost asio ssl writing part of data

My client-server app, that communicating through boost asio, usign functions:
When connection starts, client send to server bunch of requests, server send back some response.
After adding asio::ssl to project i get following problem.
Sometimes, 1/5 times, server reads only first fixed part of requests. When client disconnected, server get all missed requests.
On client side all seems good, callbakcs called with no errors and writed sizes are proper. But result from packet sniffer show that client not sending this part of requests.
Client :
Size of each "frame" located in header, first must read atleast header.
Thread Worker used for background work, and pushing ready packets to storage.
using SSLSocket = boost::asio::ssl::stream<boost::asio::ip::tcp::socket>;
class AsyncStrategy :
public NetworkStrategy
{
// other data...
void _WriteHandler(const boost::system::error_code& err, size_t bytes);
bool Connect(const boost::asio::ip::tcp::endpoint& endpoint);
void _BindMessage();
void _BindMessageRemainder(size_t size);
void _AcceptMessage(const boost::system::error_code& err_code, size_t bytes);
void _AcceptMessageRemainder(const boost::system::error_code& err_code, size_t bytes);
// to keep io_service running
void _BindTimer();
void _DumpTimer(const boost::system::error_code& error);
void _SolveProblem(const boost::system::error_code& err_code);
void _Disconnect();
bool verify_certificate(bool preverified,
boost::asio::ssl::verify_context& ctx);
PacketQuery query;
boost::array <Byte, PacketMaxSize> WriteBuff;
boost::array <Byte, PacketMaxSize> ReadBuff;
boost::asio::ip::tcp::endpoint ep;
boost::asio::io_service service;
boost::asio::deadline_timer _Timer{ service };
boost::asio::ssl::context _SSLContext;
SSLSocket sock;
boost::thread Worker;
bool _ThreadWorking;
bool _Connected = false;
};
AsyncStrategy::AsyncStrategy( MessengerAPI& api)
: API{api},_SSLContext{service,boost::asio::ssl::context::sslv23 },
sock{ service,_SSLContext }, _Timer{service},
Worker{ [&]() {
_BindTimer();
service.run();
} },
_ThreadWorking{ true }
{
_SSLContext.set_verify_mode(boost::asio::ssl::verify_peer);
_SSLContext.set_verify_callback(
boost::bind(&AsyncStrategy::verify_certificate, this, _1, _2));
_SSLContext.load_verify_file("ca.pem");
}
bool AsyncStrategy::verify_certificate(bool preverified,
boost::asio::ssl::verify_context& ctx)
{
return preverified;
}
void AsyncStrategy::_BindMessage()
{
boost::asio::async_read(sock, buffer(ReadBuff,BaseHeader::HeaderSize()),
boost::bind(&AsyncStrategy::_AcceptMessage, this, _1, _2));
}
bool AsyncStrategy::Connect(const boost::asio::ip::tcp::endpoint& endpoint)
{
ep = endpoint;
boost::system::error_code err;
sock.lowest_layer().connect(ep, err);
if (err)
throw __ConnectionRefused{};
// need blocking handshake
sock.handshake(boost::asio::ssl::stream_base::client, err);
if (err)
throw __ConnectionRefused{};
_BindMessage();
return true;
}
void AsyncStrategy::_AcceptMessage(const boost::system::error_code& err_code, size_t bytes)
{
// checking header, to see, packet ends or not
// if there is more data in packet, read rest my binding function
// pseudocode
if( need_load_more )
_BindMessageRemainder(BytesToReceive(FrameSize));
return;
}
// if not use this bind this function next time
_CheckPacket(ReadBuff.c_array(), bytes);
_BindMessage();
}
void AsyncStrategy::_AcceptMessageRemainder(const boost::system::error_code& err_code, size_t bytes)
{
if (err_code)
{
_SolveProblem(err_code);
return;
}
_CheckPacket(ReadBuff.c_array(), bytes + BaseHeader::HeaderSize());
_BindMessage();
}
bool AsyncStrategy::Send(const TransferredData& Data)
{
// alreay known, that that data fits in buffer
Data.ToBuffer(WriteBuff.c_array());
boost::asio::async_write(sock,
buffer(WriteBuff, Data.NeededSize()),
boost::bind(&AsyncStrategy::_WriteHandler, this, _1, _2));
return true;
}
void AsyncStrategy::_WriteHandler(const boost::system::error_code& err, size_t bytes)
{
if (err)
_SolveProblem(err);
}
After removing all ssl stuff, data transfer is normal. As i mentioned, all works properly before ssl integration.
Finding solution, i discovered that if send with delay, tried 200 ms, all data transferring normally.
Win10, boost 1.60, OpenSSL 1.0.2n
I guess there may be an error in my code, but I tried almost everything I thought. Looking for advice.
We can't see how Send is actually called.
Perhaps it needs to be synchronized.
We can that it reuses the same buffer each time, so two writes overlapping will clobber that buffer.
We can also see that you're not verifying that the size of the Data argument fits into the PacketMaxSize buffer.
This means you will not only lose data if you exceed the expected buffer size, it will also invoke Undefined Behaviour

boost asio tcp async read/write

i have an understanding problem how boost asio handles this:
When I watch my request response on client side, I can use following boost example Example
But I don't understand what happens if the server send every X ms some status information to the client. Have I open a serperate socket for this or can my client difference which is the request, response and the cycleMessage ?
Can it happen, that the client send a Request and read is as cycleMessage? Because he is also waiting for async_read because of this Message?
class TcpConnectionServer : public boost::enable_shared_from_this<TcpConnectionServer>
{
public:
typedef boost::shared_ptr<TcpConnectionServer> pointer;
static pointer create(boost::asio::io_service& io_service)
{
return pointer(new TcpConnectionServer(io_service));
}
boost::asio::ip::tcp::socket& socket()
{
return m_socket;
}
void Start()
{
SendCycleMessage();
boost::asio::async_read(
m_socket, boost::asio::buffer(m_data, m_dataSize),
boost::bind(&TcpConnectionServer::handle_read_data, shared_from_this(), boost::asio::placeholders::error));
}
private:
TcpConnectionServer(boost::asio::io_service& io_service)
: m_socket(io_service),m_cycleUpdateRate(io_service,boost::posix_time::seconds(1))
{
}
void handle_read_data(const boost::system::error_code& error_code)
{
if (!error_code)
{
std::string answer=doSomeThingWithData(m_data);
writeImpl(answer);
boost::asio::async_read(
m_socket, boost::asio::buffer(m_data, m_dataSize),
boost::bind(&TcpConnectionServer::handle_read_data, shared_from_this(), boost::asio::placeholders::error));
}
else
{
std::cout << error_code.message() << "ERROR DELETE READ \n";
// delete this;
}
}
void SendCycleMessage()
{
std::string data = "some usefull data";
writeImpl(data);
m_cycleUpdateRate.expires_from_now(boost::posix_time::seconds(1));
m_cycleUpdateRate.async_wait(boost::bind(&TcpConnectionServer::SendTracedParameter,this));
}
void writeImpl(const std::string& message)
{
m_messageOutputQueue.push_back(message);
if (m_messageOutputQueue.size() > 1)
{
// outstanding async_write
return;
}
this->write();
}
void write()
{
m_message = m_messageOutputQueue[0];
boost::asio::async_write(
m_socket,
boost::asio::buffer(m_message),
boost::bind(&TcpConnectionServer::writeHandler, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void writeHandler(const boost::system::error_code& error, const size_t bytesTransferred)
{
m_messageOutputQueue.pop_front();
if (error)
{
std::cerr << "could not write: " << boost::system::system_error(error).what() << std::endl;
return;
}
if (!m_messageOutputQueue.empty())
{
// more messages to send
this->write();
}
}
boost::asio::ip::tcp::socket m_socket;
boost::asio::deadline_timer m_cycleUpdateRate;
std::string m_message;
const size_t m_sizeOfHeader = 5;
boost::array<char, 5> m_headerData;
std::vector<char> m_bodyData;
std::deque<std::string> m_messageOutputQueue;
};
With this implementation I will not need boost::asio::strand or? Because I will not modify the m_messageOutputQueue from an other thread.
But when I have on my client side an m_messageOutputQueue which i can access from an other thread on this point I will need strand? Because then i need the synchronization? Did I understand something wrong?
The differentiation of the message is part of your application protocol.
ASIO merely provides transport.
Now, indeed if you want to have a "keepalive" message you will have to design your protocol in such away that the client can distinguish the messages.
The trick is to think of it at a higher level. Don't deal with async_read on the client directly. Instead, make async_read put messages on a queue (or several queues; the status messages could not even go in a queue but supersede a previous non-handled status update, e.g.).
Then code your client against those queues.
A simple thing that is typically done is to introduce message framing and a message type id:
FRAME offset 0: message length(N)
FRAME offset 4: message data
FRAME offset 4+N: message checksum
FRAME offset 4+N+sizeof checksum: sentinel (e.g. 0x00, or a larger unique signature)
The structure there makes the protocol more extensible. It's easy to add encryption/compression without touch all other code. There's built-in error detection etc.

How to implement a WebSocket server serving only 1 client at a time

I'm using WebSocket++ library to implement a WebSocket server.
Due to the characteristic of my server, I would like it to serve only 1 client at a time. That is, once a client has been connected to my server, I would like it to stop listening from other potential clients until the already connected client disconnects.
Currently my server looks like:
typedef websocketpp::server<websocketpp::config::asio> server;
static void onReceiveData(server *s, websocketpp::connection_hdl hdl, server::message_ptr msg)
{
std::string payload(msg->get_payload());
// process inside payload
websocketpp::lib::error_code ec;
s->send(hdl, payload, websocketpp::frame::opcode::BINARY, ec);
}
int main(void)
{
server myServer;
myServer.init_asio();
myServer.set_message_handler(websocketpp::lib::bind(&onReceiveData, &myServer, websocketpp::lib::placeholders::_1, websocketpp::lib::placeholders::_2));
myServer.listen(9002);
myServer.start_accept();
myServer.run();
}
How should I pause and resume listening, or is there any parameter for limiting number of concurrent clients?
Use validate to decide when to accept or refuse a connection:
static bool accept_new_connections = true;
bool validate(server *, websocketpp::connection_hdl) {
return accept_new_connections;
}
void on_open(server *, websocketpp::connection_hdl hdl) {
accept_new_connections = false;
}
void on_close(server *, websocketpp::connection_hdl hdl) {
accept_new_connections = true;
}
Obviously to make above work you will need to set additional handlers for open, close and validate:
myServer.set_open_handler(bind(&on_open, &echo_server, ::_1));
myServer.set_close_handler(bind(&on_close, &echo_server, ::_1));
myServer.set_validate_handler(bind(&validate, &echo_server, ::_1));

Acceptor and Problems with Async_Accept

See code. :P
I am able to receive new connections before async_accept() has been called. My delegate function is also never called so I can't manage any connections I receive, rendering the new connections useless. ;)
So here's my question. Is there a way to prevent the Boost ASIO acceptor from getting new connections on its own and only getting connections from async_accept()?
Thanks!
AlexSocket::AlexSocket(boost::asio::io_service& s): myService(s)
{
//none at the moment
connected = false;
listening = false;
using boost::asio::ip::tcp;
mySocket = new tcp::socket(myService);
}
AlexSocket::~AlexSocket()
{
delete mySocket;
}
bool AlexSocket::StartListening(int port)
{
bool didStart = false;
if (!this->listening)
{
//try to listen
acceptor = new tcp::acceptor(this->myService);
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), port);
acceptor->open(endpoint.protocol());
acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor->bind(endpoint);
//CAN GET NEW CONNECTIONS HERE (before async_accept is called)
acceptor->listen();
didStart = true; //probably change?
tcp::socket* tempNewSocket = new tcp::socket(this->myService);
//acceptor->async_accept(*tempNewSocket, boost::bind(&AlexSocket::NewConnection, this, tempNewSocket, boost::asio::placeholders::error) );
}
else //already started!
return false;
this->listening = didStart;
return didStart;
}
//this function is never called :(
void AlexSocket::NewConnection(tcp::socket* s, const boost::system::error_code& error)
{
cout << "New Connection Made" << endl;
//Start new accept async
tcp::socket* tempNewSocket = new tcp::socket(this->myService);
acceptor->async_accept(*tempNewSocket, boost::bind(&AlexSocket::NewConnection, this, tempNewSocket, boost::asio::placeholders::error) );
}
bool AlexSocket::ConnectToServer(std::string toConnectTo, string port)
{
if (connected)
return false;
this->serverConnectedTo = toConnectTo;
this->serverPort = port;
ip::tcp::resolver resolver(myService);
ip::tcp::resolver::query newQuery(toConnectTo, port);
ip::tcp::resolver::iterator myIter = resolver.resolve(newQuery);
ip::tcp::resolver::iterator end;
//error
boost::system::error_code error = boost::asio::error::host_not_found;
//try each endpoint
bool connected = false;
while (error && myIter != end)
{
ip::tcp::endpoint endpoint = *myIter++;
std::cout << endpoint << std::endl;
mySocket->close();
mySocket->connect(*myIter, error);
if (error)
{
//try to connect, if it didn't work return false
cout << "Did not Connect" << endl << error << endl;
}
else
{
//was able to connect
cout << "Connected!" << endl;
connected = true;
}
myIter++;
}
this->connected = connected;
return connected;
}
EDIT:
I've changed my code to reflect what the answers so far have said. I am passing in an io_service to the ctor of my class. As you can see below, main is NOT calling run on the service, so I would assume that nothing should be able to connect right?
I have put my debugger on the listen() line and went to "canyouseeme.org". Typed in 57422 and hit Connect. Couldn't. Ran the listen() line. Was able to connect. This shouldn't be possible right? Like never? :(
No idea what to do anymore. main() is below.
int main()
{
boost::asio::io_service s;
AlexSocket test(s);
test.StartListening(57422);
test.ConnectToServer("localhost", "57422");
cout << "Enter something to quit" << endl;
int a2;
cin >> a2;
return 0;
}
So here's my question. Is there a way to prevent the Boost ASIO acceptor from getting new connections on its own and only getting connections from async_accept()?
Why do you think this is happening? If you posted the complete code, that would greatly help. When I take your snippet and put a boilerplate main and io_service::run() around it, everything works fine.
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>
using namespace boost::asio;
class Socket {
public:
Socket(
io_service& io_service
) :
_io_service( io_service ),
_acceptor( new ip::tcp::acceptor(io_service) )
{
}
bool start(int port)
{
//try to listen
ip::tcp::endpoint endpoint(ip::tcp::v4(), port);
_acceptor->open(endpoint.protocol());
_acceptor->set_option(ip::tcp::acceptor::reuse_address(true));
_acceptor->bind(endpoint);
//CAN GET NEW CONNECTIONS HERE (before async_accept is called)
_acceptor->listen();
ip::tcp::socket* temp = new ip::tcp::socket( _io_service );
_acceptor->async_accept(
*temp,
boost::bind(
&Socket::NewConnection,
this,
temp,
boost::asio::placeholders::error
)
);
}
void NewConnection(
ip::tcp::socket* s,
const boost::system::error_code& error
)
{
std::cout << "New Connection Made" << std::endl;
//Start new accept async
ip::tcp::socket* temp = new ip::tcp::socket( _io_service );
_acceptor->async_accept(
*temp,
boost::bind(
&Socket::NewConnection,
this,
temp,
boost::asio::placeholders::error
)
);
}
private:
io_service& _io_service;
ip::tcp::acceptor* _acceptor;
};
int
main()
{
io_service foo;
Socket sock( foo );
sock.start(1234);
foo.run();
return 0;
}
compile and run:
macmini:~ samm$ g++ -lboost_system accept.cc
macmini:~ samm$ ./a.out
New Connection Made
telnet from another terminal
macmini:~ samm$ telnet 127.0.0.1 1234
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
I think you are mixing different things here.
On the one hand, you are creating a socket for data exchange. A socket is nothing more than an endpoint of an inter-process communication flow across a computer network. Your boost::asio::tcp::socket uses the TCP-protocoll for the communication; but in general, a socket can use other protocols. For opening a tcp-socket, one uses generally the sequence open-bind-listen-accept on the host.
On the other hand, you analyse the (underlying) TCP-connection.
So there are two different things here. While for the socket the connection is considered "established" only after the "accept" of the host, the underlying TCP-connection is already established after the client connects to a listening socket. (One the server side, that connection is put on a stack, from which it is dequeue when you call accept()).
So the only way to prohibit connection in your case, is not to call listen().
If you are truly getting a new connection at the point when you call acceptor->listen() then I am puzzled by that. What are you using to determine whether you've gotten a connection or not? The io_service is typically quite "reactive" in that it only reacts to events that it has been explicitly told to react to.
In your example above, the only thing I see that would cause a "new connection" to be initiated is calling async_accept. Additionally, what you described makes little sense from a low-level sockets standpoint (using BSD sockets, typically you must call bind, listen, and accept in that order, and only then can a new connection be made).
My suspicion is that you've actually got some faulty logic somewhere. Who calls StartListening and how often is it called (it should only need to be called once). You've gone through a bunch of extra effort to setup your acceptor object that's usually not necessary in Asio - you can typically just use the acceptor constructor to create an acceptor with all the parameters you need, and then just call async_accept:
acceptor = new tcp::acceptor(
this->myService,
boost::asio::ip::tcp::endpoint(
boost::asio::ip::tcp::v4(),
port),
true);
tcp::socket* tempNewSocket = new tcp::socket(this->myService);
acceptor->async_accept(
*tempNewSocket,
boost::bind(
&AlexSocket::NewConnection,
this,
tempNewSocket,
boost::asio::placeholders::error) );